aboutsummaryrefslogtreecommitdiff
path: root/tw/services/files
diff options
context:
space:
mode:
authorTimo Wilken2024-03-09 14:52:56 +0100
committerTimo Wilken2024-03-10 16:19:00 +0100
commitde20fc8d904643ffe6957febfc6a24e57c12b512 (patch)
tree8177459e40786bd432a37c5833f26350fb689356 /tw/services/files
parentda5e9d5ee98dfc216eb7e3b1559c09f4bf868bf6 (diff)
Separate home service into PIM, dev env and graphical parts
This means we only instantiate Shepherd and mcron services if we really need them, to avoid annoyance on servers.
Diffstat (limited to 'tw/services/files')
-rw-r--r--tw/services/files/XCompose161
-rw-r--r--tw/services/files/Xresources13
-rw-r--r--tw/services/files/aerc/accounts.conf97
-rw-r--r--tw/services/files/aerc/accounts.work.conf26
-rw-r--r--tw/services/files/aerc/aerc.conf157
-rw-r--r--tw/services/files/aerc/binds.conf194
-rwxr-xr-xtw/services/files/aerc/filters/colorize.ansi134
-rw-r--r--tw/services/files/cursors.ini4
-rw-r--r--tw/services/files/dunstrc98
-rw-r--r--tw/services/files/emacs-init.el1027
-rw-r--r--tw/services/files/emacs-packages/actionlint.el147
-rw-r--r--tw/services/files/emacs-packages/alidist-mode.el170
-rw-r--r--tw/services/files/emacs-packages/bemscript-mode.el92
-rw-r--r--tw/services/files/emacs-packages/environmentd-mode.el46
-rw-r--r--tw/services/files/emacs-packages/flymake-guile.el123
-rw-r--r--tw/services/files/emacs-packages/ifm-mode.el18
-rw-r--r--tw/services/files/emacs-packages/pam-env-mode.el45
-rw-r--r--tw/services/files/emacs-packages/vcard-mode.el56
-rw-r--r--tw/services/files/emacsclient.desktop22
-rw-r--r--tw/services/files/gtk2.ini20
-rw-r--r--tw/services/files/gtk3.ini16
-rw-r--r--tw/services/files/i3.conf273
-rw-r--r--tw/services/files/khal.conf44
-rw-r--r--tw/services/files/khard.conf22
-rw-r--r--tw/services/files/kitty.conf1337
-rw-r--r--tw/services/files/mimeapps.list57
-rw-r--r--tw/services/files/newsboat.conf29
-rwxr-xr-xtw/services/files/passmenu93
-rw-r--r--tw/services/files/picom.conf304
-rw-r--r--tw/services/files/rofi.rasi41
-rwxr-xr-xtw/services/files/sessionmenu38
-rw-r--r--tw/services/files/vdirsyncer.conf87
-rw-r--r--tw/services/files/vimrc60
-rwxr-xr-xtw/services/files/volume60
-rw-r--r--tw/services/files/zathurarc32
35 files changed, 5143 insertions, 0 deletions
diff --git a/tw/services/files/XCompose b/tw/services/files/XCompose
new file mode 100644
index 00000000..cb73e833
--- /dev/null
+++ b/tw/services/files/XCompose
@@ -0,0 +1,161 @@
+# -*- mode: conf-colon -*-
+## Locale defaults
+include "%S/en_US.UTF-8/Compose"
+# This file apparently doesn't exist for all locales, including en_GB.
+# Most useful things live in en_US, included above.
+include "%L"
+
+## Dashes
+<Multi_key> <minus> <minus> <period> : "–" U2013 # EN DASH
+<Multi_key> <minus> <minus> <space> : "–" U2013 # EN DASH
+<Multi_key> <minus> <n> : "–" U2013 # EN DASH
+<Multi_key> <minus> <minus> <minus> : "—" U2014 # EM DASH
+<Multi_key> <minus> <m> : "—" U2014 # EM DASH
+<Multi_key> <minus> <1> : "—" U2014 # EM DASH
+<Multi_key> <minus> <2> : "⸺" U2E3A # TWO-EM DASH
+<Multi_key> <minus> <3> : "⸻" U2E3B # THREE-EM DASH
+<Multi_key> <minus> <b> : "―" U2015 # HORIZONTAL BAR
+<Multi_key> <minus> <h> <b> : "―" U2015 # HORIZONTAL BAR
+
+## Mathematical operators
+<Multi_key> <minus> <x> : "−" U2212 # MINUS SIGN
+<Multi_key> <minus> <plus> : "−" U2212 # MINUS SIGN
+<Multi_key> <minus> <equal> <equal> : "≡" U2261 # IDENTICAL TO
+<Multi_key> <minus> <equal> <slash> : "≢" U2262 # NOT IDENTICAL TO
+<Multi_key> <asciitilde> <asciitilde> : "≈" U2248 # ALMOST EQUAL TO
+<Multi_key> <asciitilde> <equal> : "≃" similarequal # ASYMPTOTICALLY EQUAL TO
+<Multi_key> <equal> <asciitilde> : "≃" similarequal # ASYMPTOTICALLY EQUAL TO
+<Multi_key> <asciitilde> <less> : "≲" U2272 # LESS-THAN OR EQUIVALENT TO
+<Multi_key> <less> <asciitilde> : "≲" U2272 # LESS-THAN OR EQUIVALENT TO
+<Multi_key> <asciitilde> <greater> : "≳" U2273 # GREATER-THAN OR EQUIVALENT TO
+<Multi_key> <greater> <asciitilde> : "≳" U2273 # GREATER-THAN OR EQUIVALENT TO
+<Multi_key> <less> <slash> <minus> : "↚" U219A # LEFTWARDS ARROW WITH STROKE
+<Multi_key> <minus> <slash> <greater> : "↛" U219B # RIGHTWARDS ARROW WITH STROKE
+<Multi_key> <less> <slash> <greater> : "↮" U21AE # LEFT RIGHT ARROW WITH STROKE
+<Multi_key> <slash> <E> : "∄" U2204 # THERE DOES NOT EXIST
+<Multi_key> <e> <slash> : "∉" U2209 # NOT AN ELEMENT OF
+<Multi_key> <slash> <e> : "∌" U220C # DOES NOT CONTAIN AS MEMBER
+<Multi_key> <slash> <bar> : "∤" U2224 # DOES NOT DIVIDE
+<Multi_key> <bar> <slash> : "∤" U2224 # DOES NOT DIVIDE
+<Multi_key> <backslash> <slash> <slash> : "∦" U2226 # NOT PARALLEL TO
+<Multi_key> <slash> <backslash> <slash> : "∦" U2226 # NOT PARALLEL TO
+<Multi_key> <asciitilde> <slash> : "≁" U2241 # NOT TILDE
+<Multi_key> <slash> <asciitilde> : "≁" U2241 # NOT TILDE
+
+## Symbols
+<Multi_key> <d> <i> : "⌀" U2300 # DIAMETER SIGN
+# Note: also defined as <Multi_key> <N> <o>, but that's hard to remember!
+<Multi_key> <n> <o> : "№" numerosign # NUMERO SIGN
+# <Multi_key> <minus> <O> is already reserved for Ō
+<Multi_key> <O> <minus> : "⊖" U2296 # ○ - CIRCLED MINUS
+# <Multi_key> <period> <O> is already reserved for Ȯ
+<Multi_key> <O> <period> : "⊙" U2299 # ○ - CIRCLED DOT
+
+## Whitespace
+<Multi_key> <space> <b> : " " U00A0 # NO-BREAK SPACE (~)
+<Multi_key> <space> <n> : " " U2002 # EN SPACE (\enspace)
+<Multi_key> <space> <m> : " " U2003 # EM SPACE (\quad)
+<Multi_key> <space> <3> : " " U2004 # THREE-PER-EM SPACE
+<Multi_key> <space> <4> : " " U2005 # FOUR-PER-EM SPACE
+<Multi_key> <space> <6> : " " U2006 # SIX-PER-EM SPACE
+<Multi_key> <space> <f> : " " U2007 # FIGURE SPACE
+<Multi_key> <space> <p> : " " U2008 # PUNCTUATION SPACE
+<Multi_key> <space> <t> : " " U2009 # THIN SPACE (\,)
+<Multi_key> <space> <h> : " " U200A # HAIR SPACE
+<Multi_key> <space> <x> : " " U205F # MEDIUM MATHEMATICAL SPACE (may be used around operators)
+
+## Greek uppercase
+<Multi_key> <g> <A> : "Α" U0391
+<Multi_key> <g> <B> : "Β" U0392
+<Multi_key> <g> <G> : "Γ" U0393
+<Multi_key> <g> <D> : "Δ" U0394
+<Multi_key> <g> <E> : "Ε" U0395
+<Multi_key> <g> <Z> : "Ζ" U0396
+<Multi_key> <g> <H> : "Η" U0397
+<Multi_key> <G> <T> : "Θ" U0398
+<Multi_key> <g> <I> : "Ι" U0399
+<Multi_key> <g> <K> : "Κ" U039A
+<Multi_key> <g> <L> : "Λ" U039B
+<Multi_key> <g> <M> : "Μ" U039C
+<Multi_key> <g> <N> : "Ν" U039D
+<Multi_key> <g> <C> : "Ξ" U039E
+<Multi_key> <g> <O> : "Ο" U039F
+<Multi_key> <g> <P> : "Π" U03A0
+<Multi_key> <g> <R> : "Ρ" U03A1
+<Multi_key> <g> <S> : "Σ" U03A3
+<Multi_key> <g> <T> : "Τ" U03A4
+<Multi_key> <g> <Y> : "Υ" U03A5
+<Multi_key> <g> <F> : "Φ" U03A6
+<Multi_key> <g> <X> : "Χ" U03A7
+<Multi_key> <g> <U> : "Ψ" U03A8
+<Multi_key> <g> <W> : "Ω" U03A9
+
+## Greek lowercase
+<Multi_key> <g> <a> : "α" U03B1
+<Multi_key> <g> <b> : "β" U03B2
+<Multi_key> <g> <g> : "γ" U03B3
+<Multi_key> <g> <d> : "δ" U03B4
+<Multi_key> <g> <e> : "ε" U03B5
+<Multi_key> <g> <z> : "ζ" U03B6
+<Multi_key> <g> <h> : "η" U03B7
+<Multi_key> <G> <t> : "θ" U03B8
+<Multi_key> <g> <i> : "ι" U03B9
+<Multi_key> <g> <k> : "κ" U03BA
+<Multi_key> <g> <l> : "λ" U03BB
+<Multi_key> <g> <m> : "μ" U03BC
+<Multi_key> <g> <n> : "ν" U03BD
+<Multi_key> <g> <c> : "ξ" U03BE
+<Multi_key> <g> <o> : "ο" U03BF
+<Multi_key> <g> <p> : "π" U03C0
+<Multi_key> <g> <r> : "ρ" U03C1
+<Multi_key> <G> <s> : "ς" U03C2
+<Multi_key> <g> <s> : "σ" U03C3
+<Multi_key> <g> <t> : "τ" U03C4
+<Multi_key> <g> <y> : "υ" U03C5
+<Multi_key> <g> <f> : "φ" U03C6
+<Multi_key> <G> <f> : "ɸ" U03D5
+<Multi_key> <g> <x> : "χ" U03C7
+<Multi_key> <g> <u> : "ψ" U03C8
+<Multi_key> <g> <w> : "ω" U03C9
+
+## Weird symbols
+# TODO: give these typeable names!
+<Multi_key> <U2243> <U0338> : "≄" U2244 # NOT ASYMPTOTICALLY EQUAL TO
+<Multi_key> <approximate> <U0338> : "≇" U2247 # NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO
+<Multi_key> <U2248> <U0338> : "≉" U2249 # NOT ALMOST EQUAL TO
+<Multi_key> <U224D> <U0338> : "≭" U226D # NOT EQUIVALENT TO
+<Multi_key> <less> <U0338> : "≮" U226E # NOT LESS-THAN
+<Multi_key> <leftcaret> <U0338> : "≮" U226E # NOT LESS-THAN
+<Multi_key> <greater> <U0338> : "≯" U226F # NOT GREATER-THAN
+<Multi_key> <rightcaret> <U0338> : "≯" U226F # NOT GREATER-THAN
+<Multi_key> <lessthanequal> <U0338> : "≰" U2270 # NEITHER LESS-THAN NOR EQUAL TO
+<Multi_key> <greaterthanequal> <U0338> : "≱" U2271 # NEITHER GREATER-THAN NOR EQUAL TO
+<Multi_key> <U2272> <U0338> : "≴" U2274 # NEITHER LESS-THAN NOR EQUIVALENT TO
+<Multi_key> <U2273> <U0338> : "≵" U2275 # NEITHER GREATER-THAN NOR EQUIVALENT TO
+<Multi_key> <U2276> <U0338> : "≸" U2278 # NEITHER LESS-THAN NOR GREATER-THAN
+<Multi_key> <U2277> <U0338> : "≹" U2279 # NEITHER GREATER-THAN NOR LESS-THAN
+<Multi_key> <U227A> <U0338> : "⊀" U2280 # DOES NOT PRECEDE
+<Multi_key> <U227B> <U0338> : "⊁" U2281 # DOES NOT SUCCEED
+<Multi_key> <includedin> <U0338> : "⊄" U2284 # NOT A SUBSET OF
+<Multi_key> <leftshoe> <U0338> : "⊄" U2284 # NOT A SUBSET OF
+<Multi_key> <includes> <U0338> : "⊅" U2285 # NOT A SUPERSET OF
+<Multi_key> <rightshoe> <U0338> : "⊅" U2285 # NOT A SUPERSET OF
+<Multi_key> <U2286> <U0338> : "⊈" U2288 # NEITHER A SUBSET OF NOR EQUAL TO
+<Multi_key> <U2287> <U0338> : "⊉" U2289 # NEITHER A SUPERSET OF NOR EQUAL TO
+<Multi_key> <righttack> <U0338> : "⊬" U22AC # DOES NOT PROVE
+<Multi_key> <U22A8> <U0338> : "⊭" U22AD # NOT TRUE
+<Multi_key> <U22A9> <U0338> : "⊮" U22AE # DOES NOT FORCE
+<Multi_key> <U22AB> <U0338> : "⊯" U22AF # NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNSTILE
+<Multi_key> <U227C> <U0338> : "⋠" U22E0 # DOES NOT PRECEDE OR EQUAL
+<Multi_key> <U227D> <U0338> : "⋡" U22E1 # DOES NOT SUCCEED OR EQUAL
+<Multi_key> <U2291> <U0338> : "⋢" U22E2 # NOT SQUARE IMAGE OF OR EQUAL TO
+<Multi_key> <U2292> <U0338> : "⋣" U22E3 # NOT SQUARE ORIGINAL OF OR EQUAL TO
+<Multi_key> <U22B2> <U0338> : "⋪" U22EA # NOT NORMAL SUBGROUP OF
+<Multi_key> <U22B3> <U0338> : "⋫" U22EB # DOES NOT CONTAIN AS NORMAL SUBGROUP
+<Multi_key> <U22B4> <U0338> : "⋬" U22EC # NOT NORMAL SUBGROUP OF OR EQUAL TO
+<Multi_key> <U22B5> <U0338> : "⋭" U22ED # DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL
+<Multi_key> <U2ADD> <U0338> : "⫝̸" U2ADC # FORKING
+<Multi_key> <underscore> <U2282> : "⊆" U2286 # _ ⊂ SUBSET OF OR EQUAL TO
+<Multi_key> <U2282> <underscore> : "⊆" U2286 # ⊂ _ SUBSET OF OR EQUAL TO
+<Multi_key> <underscore> <U2283> : "⊇" U2287 # _ ⊃ SUPERSET OF OR EQUAL TO
+<Multi_key> <U2283> <underscore> : "⊇" U2287 # ⊃ _ SUPERSET OF OR EQUAL TO
diff --git a/tw/services/files/Xresources b/tw/services/files/Xresources
new file mode 100644
index 00000000..ba534392
--- /dev/null
+++ b/tw/services/files/Xresources
@@ -0,0 +1,13 @@
+!! ~/.Xresources: X resources and settings
+
+*TkTheme: clam
+
+Xft.antialias: true
+Xft.dpi: 96
+Xft.hinting: true
+Xft.hintstyle: hintfull
+Xft.lcdfilter: lcddefault
+Xft.rgba: rgb
+
+Xcursor.theme: Catppuccin-Mocha-Dark-Cursors
+Xcursor.size: 16
diff --git a/tw/services/files/aerc/accounts.conf b/tw/services/files/aerc/accounts.conf
new file mode 100644
index 00000000..5325ee03
--- /dev/null
+++ b/tw/services/files/aerc/accounts.conf
@@ -0,0 +1,97 @@
+# Note: when adding/removing/editing an account name here, edit
+# [messages:account=] and [messages:folder=] sections in binds.conf to match!
+
+[Mythic]
+source = imaps://timo%40twilken.net@oncilla.mythic-beasts.com
+outgoing = smtps+plain://timo%40twilken.net@smtp-auth.mythic-beasts.com
+source-cred-cmd = pass www/mythic-beasts/email/timo | head -1
+outgoing-cred-cmd = pass www/mythic-beasts/email/timo | head -1
+default = INBOX
+from = Timo Wilken <timo@twilken.net>
+archive = Archive
+copy-to = Sent
+postpone = Drafts
+folders-sort = INBOX,Archive,Sent,Drafts
+cache-headers = true
+pgp-auto-sign = true
+pgp-key-id = 53EC3C06856883DD92355BC22FC78504681F69B0
+pgp-opportunistic-encrypt = true
+
+[CERN]
+# https://man.sr.ht/~rjarry/aerc/providers/microsoft.md#office365-with-xoauth2
+source = imaps+xoauth2://timo.wilken%40cern.ch@outlook.office365.com?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+outgoing = smtp+xoauth2://timo.wilken%40cern.ch@smtp.office365.com:587?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+# To authorize for the first time (to get refresh token):
+# mutt_oauth2.py ~/.local/share/aerc/cern.tokens --authorize --authflow authcode --provider microsoft \
+# --email timo.wilken@cern.ch --client-id 9e5f94bc-e8a4-4e73-b8be-63364c29d753 --client-secret '' \
+# --encryption-pipe 'gpg --encrypt --recipient timo@twilken.net'
+# Confirm empty client secret. When done, copy-paste ?code= value from final URL to the command-line.
+# Then, to store the refresh token:
+# gpg --decrypt ~/.local/share/aerc/cern.tokens | jq -r .refresh_token | pass insert -e -f cern/exol/refresh-token
+source-cred-cmd = pass cern/exol/refresh-token
+outgoing-cred-cmd = pass cern/exol/refresh-token
+default = INBOX
+from = Timo Wilken <timo.wilken@cern.ch>
+aliases = twilken@cern.ch
+archive = Archive
+copy-to = Sent Items
+postpone = Drafts
+folders-sort = INBOX,Archive,Sent Items,Drafts
+folders-exclude = Calendar,~Calendar/.*,Contacts,Conversation History,Journal,Notes,Tasks
+cache-headers = true
+pgp-auto-sign = true
+pgp-key-id = C2249BBE5E8761C943A0CFA1B7B3914BF63ACD7C
+# Don't auto-encrypt mail, even if we have the keys of all recipients.
+pgp-opportunistic-encrypt = false
+
+[Gmail]
+source = imaps://timo.21.wilken%40gmail.com@imap.gmail.com
+outgoing = smtps+login://timo.21.wilken%40gmail.com@smtp.gmail.com
+source-cred-cmd = pass www/google/app-passwords/mutt | head -1
+outgoing-cred-cmd = pass www/google/app-passwords/mutt | head -1
+default = INBOX
+from = Timo Wilken <timo.21.wilken@gmail.com>
+archive = [Gmail]/All Mail
+copy-to = [Gmail]/Sent Mail
+postpone = [Gmail]/Drafts
+folders-sort = INBOX,[Gmail]/All Mail,[Gmail]/Sent Mail,[Gmail]/Drafts
+folders-exclude = [Gmail],[Gmail]/Chats,[Gmail]/Important
+cache-headers = true
+trusted-authres = mx.google.com
+pgp-auto-sign = true
+pgp-key-id = 53EC3C06856883DD92355BC22FC78504681F69B0
+pgp-opportunistic-encrypt = true
+
+[Cantab]
+source = imaps+xoauth2://tw466%40cantab.ac.uk@outlook.office365.com?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+outgoing = smtp+xoauth2://tw466%40cantab.ac.uk@smtp-mail.outlook.com:587?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+source-cred-cmd = pass cambridge/exol-refresh-token
+outgoing-cred-cmd = pass cambridge/exol-refresh-token
+default = INBOX
+from = Timo Wilken <tw466@cantab.ac.uk>
+archive = Archive
+copy-to = Sent Items
+postpone = Drafts
+folders-sort = INBOX,Archive,Sent Items,Drafts
+folders-exclude = Calendar,~Calendar/.*,Contacts,Conversation History,Journal,Notes,Tasks
+cache-headers = true
+pgp-auto-sign = true
+pgp-key-id = 53EC3C06856883DD92355BC22FC78504681F69B0
+pgp-opportunistic-encrypt = true
+
+[Outlook]
+source = imaps+xoauth2://timo_wilken%40live.co.uk@outlook.office365.com?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+outgoing = smtp+xoauth2://timo_wilken%40live.co.uk@smtp-mail.outlook.com:587?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+source-cred-cmd = pass www/microsoft/exol-refresh-token
+outgoing-cred-cmd = pass www/microsoft/exol-refresh-token
+default = INBOX
+from = Timo Wilken <timo_wilken@live.co.uk>
+archive = Archive
+copy-to = Sent
+postpone = Drafts
+folders-sort = INBOX,Archive,Sent,Drafts
+folders-exclude = Notes
+cache-headers = true
+pgp-auto-sign = true
+pgp-key-id = 53EC3C06856883DD92355BC22FC78504681F69B0
+pgp-opportunistic-encrypt = true
diff --git a/tw/services/files/aerc/accounts.work.conf b/tw/services/files/aerc/accounts.work.conf
new file mode 100644
index 00000000..5d2fa7c7
--- /dev/null
+++ b/tw/services/files/aerc/accounts.work.conf
@@ -0,0 +1,26 @@
+[CERN]
+# https://man.sr.ht/~rjarry/aerc/providers/microsoft.md#office365-with-xoauth2
+source = imaps+xoauth2://timo.wilken%40cern.ch@outlook.office365.com?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+outgoing = smtp+xoauth2://timo.wilken%40cern.ch@smtp.office365.com:587?client_id=9e5f94bc-e8a4-4e73-b8be-63364c29d753&token_endpoint=https://login.microsoftonline.com/common/oauth2/v2.0/token&scope=https://outlook.office.com/SMTP.Send https://outlook.office.com/IMAP.AccessAsUser.All offline_access
+# To authorize for the first time (to get refresh token):
+# mutt_oauth2.py ~/.local/share/aerc/twilken.tokens --authorize --authflow authcode --provider microsoft \
+# --email timo.wilken@cern.ch --client-id 9e5f94bc-e8a4-4e73-b8be-63364c29d753 --client-secret '' \
+# --encryption-pipe 'gpg --encrypt --recipient C2249BBE5E8761C943A0CFA1B7B3914BF63ACD7C'
+# Confirm empty client secret. When done, copy-paste ?code= value from final URL to the command-line.
+# Then, to store the refresh token:
+# gpg --decrypt ~/.local/share/aerc/twilken.tokens | jq -r .refresh_token | pass insert -e -f cern/exol/refresh-token
+source-cred-cmd = pass cern/exol/refresh-token
+outgoing-cred-cmd = pass cern/exol/refresh-token
+default = INBOX
+from = Timo Wilken <timo.wilken@cern.ch>
+aliases = twilken@cern.ch
+archive = Archive
+copy-to = Sent Items
+postpone = Drafts
+folders-sort = INBOX,Archive,Sent Items,Drafts
+folders-exclude = Calendar,~Calendar/.*,Contacts,Conversation History,Journal,Notes,Tasks
+cache-headers = true
+pgp-auto-sign = true
+pgp-key-id = C2249BBE5E8761C943A0CFA1B7B3914BF63ACD7C
+# Don't auto-encrypt mail, even if we have the keys of all recipients.
+pgp-opportunistic-encrypt = false
diff --git a/tw/services/files/aerc/aerc.conf b/tw/services/files/aerc/aerc.conf
new file mode 100644
index 00000000..7473bc8b
--- /dev/null
+++ b/tw/services/files/aerc/aerc.conf
@@ -0,0 +1,157 @@
+# aerc main configuration
+
+[general]
+#default-save-path=
+# Allow world-readable accounts.conf. This is fine as I don't store any
+# passwords there, only "pass" commands.
+unsafe-accounts-conf=true
+
+[ui]
+# Describes the format for each row in a mailbox view. This field is compatible
+# with mutt's printf-like syntax.
+index-columns=num>4,flags>4,date<21,peers<17,subject<*
+column-num={{.Number}}
+column-flags={{.Flags | join ""}}
+column-date={{.DateAutoFormat .Date.Local}}
+column-peers={{.Peer | names | join ", "}}
+column-subject={{.ThreadPrefix}}{{.Subject}}
+
+# See time.Time#Format at https://godoc.org/time#Time.Format
+timestamp-format=Mon _2 Jan 2006 15:04
+
+# List of space-separated criteria to sort the messages by, see *sort*
+# command in *aerc*(1) for reference. Prefixing a criterion with "-r "
+# reverses that criterion.
+sort=-r date
+
+threading-enabled=true
+
+next-message-on-delete=false
+
+sidebar-width=24
+
+styleset-name=catppuccin
+pinned-tab-marker='📌'
+# Use box-drawing characters for vertical and horizontal borders.
+border-char-vertical=│
+border-char-horizontal=─
+# Use UTF-8 symbols to indicate PGP status of messages
+icon-unencrypted=
+icon-encrypted=🔒
+icon-signed=✔
+icon-signed-encrypted=✔🔒
+icon-unknown=✘
+icon-invalid=⚠
+
+# Activates fuzzy search in commands and their arguments: the typed string is
+# searched in the command or option in any position, and need not be
+# consecutive characters in the command or option.
+#fuzzy-complete=false
+
+[statusline]
+status-columns=left<*,centre>=,right>*
+column-left=[{{.Account}}] {{.StatusInfo}}
+column-centre={{.PendingKeys}}
+column-right={{.ContentInfo}} {{.TrayInfo}}
+
+# Defines the mode for displaying the status elements.
+# Options: text, icon
+display-mode=icon
+
+[viewer]
+# Specifies the pager to use when displaying emails. Note that some filters
+# may add ANSI codes to add color to rendered emails, so you may want to use a
+# pager which supports ANSI codes.
+pager='env LESSKEYIN=/dev/null less -iRM'
+
+# If an email offers several versions (multipart), you can configure which
+# mimetype to prefer. For example, this can be used to prefer plaintext over
+# html emails.
+alternatives=text/plain,text/html
+
+# Layout of headers when viewing a message. To display multiple headers in the
+# same row, separate them with a pipe, e.g. "From|To". Rows will be hidden if
+# none of their specified headers are present in the message.
+#header-layout=From|To,Cc|Bcc,Date,Subject
+
+[compose]
+# Default header fields to display when composing a message. To display
+# multiple headers in the same row, separate them with a pipe, e.g. "To|From".
+#header-layout=To|From,Subject
+
+# Specifies the command to be used to tab-complete email addresses. Any
+# occurrence of "%s" in the address-book-cmd will be replaced with what the
+# user has typed so far.
+#
+# The command must output the completions to standard output, one completion
+# per line. Each line must be tab-delimited, with an email address occurring as
+# the first field. Only the email address field is required. The second field,
+# if present, will be treated as the contact name. Additional fields are
+# ignored.
+#
+# This parameter can also be set per account in accounts.conf.
+address-book-cmd=khard email --parsable %s
+
+# Allow to address yourself when replying
+reply-to-self=false
+
+# Warn before sending an email that matches the specified regexp but does not
+# have any attachments. Leave empty to disable this feature.
+# Uses Go's regexp syntax, documented at https://golang.org/s/re2syntax. The
+# "(?im)" flags are set by default (case-insensitive and multi-line).
+no-attachment-warning=^[^>]*(attach|Anhang|angehängt)
+
+[filters]
+# Filters allow you to pipe an email body through a shell command to render
+# certain emails differently, e.g. highlighting them with ANSI escape codes.
+#
+# The commands are invoked with sh -c. The following folders are appended to
+# the system $PATH to allow referencing filters from their name only:
+#
+# ${XDG_CONFIG_HOME:-~/.config}/aerc/filters
+# ${XDG_DATA_HOME:-~/.local/share}/aerc/filters
+# $PREFIX/share/aerc/filters
+# /usr/share/aerc/filters
+#
+# The following variables are defined in the filter command environment:
+#
+# AERC_MIME_TYPE the part MIME type/subtype
+# AERC_FILENAME the attachment filename (if any)
+#
+# The first filter which matches the email's mimetype will be used, so order
+# them from most to least specific.
+#
+# You can also match on non-mimetypes, by prefixing with the header to match
+# against (non-case-sensitive) and a comma, e.g. subject,text will match a
+# subject which contains "text". Use header,~regex to match against a regex.
+text/plain=colorize.ansi
+text/calendar=calendar
+message/delivery-status=colorize.ansi
+message/rfc822=colorize.ansi
+#text/html=pandoc -f html -t plain | colorize
+#text/html=html | colorize
+text/html=lynx -display_charset=UTF-8 -force_html -dump -stdin
+#text/*=bat -fP --file-name="$AERC_FILENAME"
+#application/x-sh=bat -fP -l sh
+#image/*=catimg -w $(tput cols) -
+#subject,~Git(hub|lab)=lolcat -f
+#from,thatguywhodoesnothardwraphismessages=fmt -w 72 | colorize
+
+[openers]
+# Openers allow you to specify the command to use for the :open action on a
+# per-MIME-type basis.
+#
+# {} is expanded as the temporary filename to be opened. If it is not
+# encountered in the command, the temporary filename will be appened to the end
+# of the command.
+#
+# Examples:
+# text/html=surf -dfgms
+# text/plain=gvim {} +125
+# message/rfc822=thunderbird
+application/pdf=zathura
+image/*=imv
+
+[hooks]
+# Executed when a new email arrives in the selected folder
+new-email=dunstify -a aerc -i mail-unread "New mail from $AERC_FROM_NAME" "$AERC_SUBJECT"
diff --git a/tw/services/files/aerc/binds.conf b/tw/services/files/aerc/binds.conf
new file mode 100644
index 00000000..0e88b571
--- /dev/null
+++ b/tw/services/files/aerc/binds.conf
@@ -0,0 +1,194 @@
+# Binds are of the form <key sequence> = <command to run>
+# To use '=' in a key sequence, substitute it with "Eq": "<Ctrl+Eq>"
+# If you wish to bind #, you can wrap the key sequence in quotes: "#" = quit
+<C-p> = :prev-tab<Enter>
+<C-n> = :next-tab<Enter>
+<C-t> = :term<Enter>
+? = :help keys<Enter>
+
+[messages]
+q = :quit<Enter>
+
+j = :next<Enter>
+<Down> = :next<Enter>
+<C-d> = :next 50%<Enter>
+<C-f> = :next 100%<Enter>
+<PgDn> = :next 100%<Enter>
+
+k = :prev<Enter>
+<Up> = :prev<Enter>
+<C-u> = :prev 50%<Enter>
+<C-b> = :prev 100%<Enter>
+<PgUp> = :prev 100%<Enter>
+g = :select 0<Enter>
+G = :select -1<Enter>
+"#" = :select<space>
+
+J = :next-folder<Enter>
+K = :prev-folder<Enter>
+H = :collapse-folder<Enter>
+L = :expand-folder<Enter>
+
+"*" = :mark -a<Enter>
+v = :mark -t<Enter>
+V = :mark -T<Enter>
+F = :flag -tx Flagged<Enter>
+<C-r> = :read -t<Enter>
+<semicolon> = :remark<Enter>
+
+T = :toggle-threads<Enter>
+
+<Enter> = :view<Enter>
+A = :archive flat<Enter>
+p = :split 25<Enter>
+e = :envelope<Enter>
+
+m = :compose<Enter>
+M = :mkdir<space>
+
+f = :forward -F<Enter>
+a = :reply -aq<Enter>
+r = :reply -q<Enter>
+
+C = :copy<space>
+S = :save<space>
+s = :move<space>
+E = :export-mbox<space>
+I = :import-mbox<space>
+
+c = :cf<space>
+$ = :check-mail<Enter>
+! = :term<space>
+| = :pipe<space>
+
+/ = :search<space>
+l = :filter<space>
+n = :next-result<Enter>
+N = :prev-result<Enter>
+<Esc> = :clear<Enter>:unmark -a<Enter>
+
+[messages:folder=Drafts]
+<Enter> = :recall<Enter>
+
+[view:account=Mythic]
+D = :move Rubbish<Enter>
+[messages:account=Mythic]
+d = :move Rubbish<Enter>
+D = :move Rubbish<Enter>
+# We can't use both :account= and :folder= at the same time, unfortunately.
+[messages:folder=Rubbish]
+d = :delete<Enter>
+D = :delete<Enter>
+
+[view:account=Wilken]
+D = :move Rubbish<Enter>
+[messages:account=Wilken]
+d = :move Rubbish<Enter>
+D = :move Rubbish<Enter>
+[messages:folder=Rubbish]
+d = :delete<Enter>
+D = :delete<Enter>
+
+[view:account=CERN]
+D = :move Deleted Items<Enter>
+[messages:account=CERN]
+d = :move Deleted Items<Enter>
+D = :move Deleted Items<Enter>
+[messages:folder=Deleted Items]
+d = :delete<Enter>
+D = :delete<Enter>
+
+[view:account=Gmail]
+D = :move [Gmail]/Bin<Enter>
+[messages:account=Gmail]
+d = :move [Gmail]/Bin<Enter>
+D = :move [Gmail]/Bin<Enter>
+[messages:folder=[Gmail]/Bin]
+d = :delete<Enter>
+D = :delete<Enter>
+[messages:folder=[Gmail]/Drafts]
+<Enter> = :recall<Enter>
+
+[view:account=Cantab]
+D = :move Deleted Items<Enter>
+[messages:account=Cantab]
+d = :move Deleted Items<Enter>
+D = :move Deleted Items<Enter>
+[messages:folder=Deleted Items]
+d = :delete<Enter>
+D = :delete<Enter>
+
+[view:account=Outlook]
+D = :move Deleted<Enter>
+[messages:account=Outlook]
+d = :move Deleted<Enter>
+D = :move Deleted<Enter>
+[messages:folder=Deleted]
+d = :delete<Enter>
+D = :delete<Enter>
+
+[view]
+/ = :toggle-key-passthrough<Enter>/
+q = :close<Enter>
+O = :open<Enter>
+S = :save<space>
+| = :pipe<space>
+A = :archive flat<Enter>
+
+<C-l> = :open-link <space>
+<C-v> = :toggle-key-passthrough<Enter>
+
+f = :forward -F<Enter>
+a = :reply -aq<Enter>
+r = :reply -q<Enter>
+
+H = :toggle-headers<Enter>
+<C-k> = :prev-part<Enter>
+<C-j> = :next-part<Enter>
+J = :next<Enter>
+K = :prev<Enter>
+
+[view::passthrough]
+$noinherit = true
+$ex = <C-x>
+<Esc> = :toggle-key-passthrough<Enter>
+
+[compose]
+# Keybindings used when the embedded terminal is not selected in the compose view.
+$noinherit = true
+$ex = <C-x>
+<C-k> = :prev-field<Enter>
+<C-j> = :next-field<Enter>
+<A-p> = :switch-account -p<Enter>
+<A-n> = :switch-account -n<Enter>
+<tab> = :next-field<Enter>
+<C-p> = :prev-tab<Enter>
+<C-n> = :next-tab<Enter>
+
+[compose::editor]
+# Keybindings used when the embedded terminal is selected in the compose view.
+$noinherit = true
+$ex = <C-x>
+<C-k> = :prev-field<Enter>
+<C-j> = :next-field<Enter>
+<C-p> = :prev-tab<Enter>
+<C-n> = :next-tab<Enter>
+
+[compose::review]
+# Keybindings used when reviewing a message to be sent
+y = :send<Enter>
+n = :abort<Enter>
+p = :postpone<Enter>
+q = :choose -o d discard abort -o p postpone postpone<Enter>
+e = :edit<Enter>
+a = :attach<space>
+d = :detach<space>
+v = :preview<Enter>
+s = :sign<Enter>
+c = :encrypt<Enter>
+
+[terminal]
+$noinherit = true
+$ex = <C-x>
+<C-p> = :prev-tab<Enter>
+<C-n> = :next-tab<Enter>
diff --git a/tw/services/files/aerc/filters/colorize.ansi b/tw/services/files/aerc/filters/colorize.ansi
new file mode 100755
index 00000000..c21fd804
--- /dev/null
+++ b/tw/services/files/aerc/filters/colorize.ansi
@@ -0,0 +1,134 @@
+#!/usr/bin/env -S awk -f
+# Copyright (c) 2022 Robin Jarry
+# Adapted for standard ANSI colors (for catppuccin theme) by Timo Wilken.
+
+BEGIN {
+ url = "\033[32m" # green
+ header = "\033[35m" # pink
+ signature = "\033[38m" # Surface 2
+ diff_meta = "\033[1;37m" # bold white
+ diff_chunk = "\033[36m" # teal
+ diff_add = "\033[32m" # green
+ diff_del = "\033[31m" # red
+ quote_1 = "\033[38;5;15m" # Subtext 0
+ quote_2 = "\033[37m" # Subtext 1
+ quote_3 = "\033[38m" # Surface 2
+ quote_4 = "\033[30m" # Surface 1
+ quote_x = "\033[30m" # Surface 1
+ bold = "\033[1m"
+ reset = "\033[0m"
+ # state
+ in_diff = 0
+ in_signature = 0
+ in_headers = 0
+ in_body = 0
+ # patterns
+ header_pattern = @/^[A-Z][[:alnum:]-]+:/
+ url_pattern = @/[a-z]{2,6}:\/\/[[:graph:]]+|(mailto:)?[[:alnum:]_\+\.~\/-]*[[:alnum:]_]@[[:lower:]][[:alnum:]\.-]*[[:lower:]]/
+ meta_pattern = @/^(diff --git|(new|deleted) file|similarity index|(rename|copy) (to|from)|index|---|\+\+\+) /
+}
+function color_quote(line) {
+ level = 0
+ quotes = ""
+ while (line ~ /^>/) {
+ level += 1
+ quotes = quotes ">"
+ line = substr(line, 2)
+ while (line ~ /^ /) {
+ quotes = quotes " "
+ line = substr(line, 2)
+ }
+ }
+ if (level == 1) {
+ color = quote_1
+ } else if (level == 2) {
+ color = quote_2
+ } else if (level == 3) {
+ color = quote_3
+ } else if (level == 4) {
+ color = quote_4
+ } else {
+ color = quote_x
+ }
+ if (line ~ meta_pattern) {
+ return color quotes bold line reset
+ } else if (line ~ /^\+/) {
+ return color quotes diff_add line reset
+ } else if (line ~ /^-/) {
+ return color quotes diff_del line reset
+ }
+ gsub(url_pattern, url "&" color, line)
+ return color quotes line reset
+}
+{
+ # Strip carriage returns from line
+ sub(/\r$/, "")
+
+ if (in_diff) {
+ if ($0 ~ /^-- ?$/) {
+ in_diff = 0
+ in_signature = 1
+ $0 = signature $0 reset
+ } else if ($0 ~ /^@@ /) {
+ gsub(/^@@[^@]+@@/, diff_chunk "&" reset)
+ } else if ($0 ~ meta_pattern) {
+ $0 = diff_meta $0 reset
+ } else if ($0 ~ /^\+/) {
+ $0 = diff_add $0 reset
+ } else if ($0 ~ /^-/) {
+ $0 = diff_del $0 reset
+ } else if ($0 !~ /^ / && $0 !~ /^$/) {
+ in_diff = 0
+ in_body = 1
+ if ($0 ~ /^>/) {
+ $0 = color_quote($0)
+ } else {
+ gsub(url_pattern, url "&" reset)
+ }
+ }
+ } else if (in_signature) {
+ gsub(url_pattern, url "&" signature)
+ $0 = signature $0 reset
+ } else if (in_headers) {
+ if ($0 ~ /^$/) {
+ in_headers = 0
+ in_body = 1
+ } else {
+ sub(header_pattern, header "&" reset)
+ gsub(url_pattern, url "&" reset)
+ }
+ } else if (in_body) {
+ if ($0 ~ /^>/) {
+ $0 = color_quote($0)
+ } else if ($0 ~ /^diff --git /) {
+ in_body = 0
+ in_diff = 1
+ $0 = diff_meta $0 reset
+ } else if ($0 ~ /^-- ?$/) {
+ in_body = 0
+ in_signature = 1
+ $0 = signature $0 reset
+ } else {
+ gsub(url_pattern, url "&" reset)
+ }
+ } else if ($0 ~ /^diff --git /) {
+ in_diff = 1
+ $0 = diff_meta $0 reset
+ } else if ($0 ~ /^-- ?$/) {
+ in_signature = 1
+ $0 = signature $0 reset
+ } else if ($0 ~ header_pattern) {
+ in_headers = 1
+ sub(header_pattern, header "&" reset)
+ gsub(url_pattern, url "&" reset)
+ } else {
+ in_body = 1
+ if ($0 ~ /^>/) {
+ $0 = color_quote($0)
+ } else {
+ gsub(url_pattern, url "&" reset)
+ }
+ }
+
+ print
+}
diff --git a/tw/services/files/cursors.ini b/tw/services/files/cursors.ini
new file mode 100644
index 00000000..c4eb9cdb
--- /dev/null
+++ b/tw/services/files/cursors.ini
@@ -0,0 +1,4 @@
+[Icon Theme]
+Name=Default
+Comment=Default Cursor Theme
+Inherits=Catppuccin-Mocha-Dark-Cursors
diff --git a/tw/services/files/dunstrc b/tw/services/files/dunstrc
new file mode 100644
index 00000000..c64d374f
--- /dev/null
+++ b/tw/services/files/dunstrc
@@ -0,0 +1,98 @@
+# See dunst(5) for all configuration options -*- mode: conf -*-
+
+[global]
+ ### Display ###
+
+ # Display notification on focused monitor. Possible modes are:
+ # mouse: follow mouse pointer
+ # keyboard: follow window with keyboard focus
+ # none: don't follow anything
+ #
+ # "keyboard" needs a window manager that exports the
+ # _NET_ACTIVE_WINDOW property.
+ # This should be the case for almost all modern window managers.
+ #
+ # If this option is set to mouse or keyboard, the monitor option
+ # will be ignored.
+ follow = mouse
+
+ ### Geometry ###
+
+ # dynamic width from 0 to 300
+ # width = (0, 300)
+ # constant width of 300
+ width = 300
+
+ # The maximum height of a single notification, excluding the frame.
+ height = 300
+
+ # Offset from the origin
+ # Leave 24pt space for polybar.
+ offset = 16x40
+
+ # Don't remove messages, if the user is idle (no mouse or keyboard input)
+ # for longer than idle_threshold seconds.
+ # Set to 0 to disable.
+ # A client can set the 'transient' hint to bypass this. See the rules
+ # section for how to disable this if necessary
+ idle_threshold = 120
+
+ ### Text ###
+
+ font = Fira Sans 12
+
+ # The format of the message. Possible variables are:
+ # %a appname
+ # %s summary
+ # %b body
+ # %i iconname (including its path)
+ # %I iconname (without its path)
+ # %p progress value if set ([ 0%] to [100%]) or nothing
+ # %n progress value if set without any extra characters
+ # %% Literal %
+ # Markup is allowed
+ # format = "<b>%s</b>\n%b"
+ format = "<span size='x-small'>%a %p</span>\n<b>%s</b>\n%b"
+
+ ### Icons ###
+
+ # Recursive icon lookup. You can set a single theme, instead of having to
+ # define all lookup paths.
+ enable_recursive_icon_lookup = true
+
+ # Set icon theme (only used for recursive icon lookup)
+ # You can also set multiple icon themes, with the leftmost one being used first.
+ # icon_theme = "Adwaita, breeze"
+ icon_theme = "Papirus-Dark, hicolor"
+
+ ### History ###
+
+ # Maximum amount of notifications kept in history
+ history_length = 100
+
+ ### Misc/Advanced ###
+
+ # dmenu path.
+ dmenu = /usr/bin/env rofi -dmenu -p dunst:
+
+ # Browser for opening urls in context menu.
+ browser = /usr/bin/env xdg-open
+
+ ### mouse
+
+ # Defines list of actions for each mouse event
+ # Possible values are:
+ # * none: Don't do anything.
+ # * do_action: Invoke the action determined by the action_name rule. If there is no
+ # such action, open the context menu.
+ # * open_url: If the notification has exactly one url, open it. If there are multiple
+ # ones, open the context menu.
+ # * close_current: Close current notification.
+ # * close_all: Close all notifications.
+ # * context: Open context menu for the notification.
+ # * context_all: Open context menu for all notifications.
+ # These values can be strung together for each mouse event, and
+ # will be executed in sequence.
+ mouse_left_click = close_current
+ mouse_middle_click = do_action, close_current
+ mouse_right_click = close_all
diff --git a/tw/services/files/emacs-init.el b/tw/services/files/emacs-init.el
new file mode 100644
index 00000000..0f20782d
--- /dev/null
+++ b/tw/services/files/emacs-init.el
@@ -0,0 +1,1027 @@
+;;; init.el --- Emacs configuration. -*- lexical-binding: t -*-
+;;; Commentary:
+;;; Code:
+
+(startup-redirect-eln-cache
+ (expand-file-name "emacs/eln" (or (getenv "XDG_CACHE_HOME") "~/.cache/")))
+(add-hook 'after-init-hook #'native-compile-prune-cache)
+
+;; Load settings set through Custom.
+;; (setq custom-file (locate-user-emacs-file "custom.el"))
+;; (when (file-readable-p custom-file)
+;; (load custom-file))
+
+(defun tw/xdg-emacs-subdir (type name &optional create)
+ "Get the name of a file or directory called NAME under $XDG_<TYPE>_HOME/emacs.
+If CREATE is true and the resulting directory does not exist, create it."
+ (let ((dir (expand-file-name
+ (concat "emacs/" (string-trim-right name "/"))
+ (pcase type
+ ('cache (or (getenv "XDG_CACHE_HOME") "~/.cache/"))
+ ('config (or (getenv "XDG_CONFIG_HOME") "~/.config/"))
+ ('data (or (getenv "XDG_DATA_HOME") "~/.local/share/"))
+ ;; The following two are Guix/GuixSD extensions.
+ ('log (or (getenv "XDG_LOG_HOME") "~/.local/var/log/"))
+ ('state (or (getenv "XDG_STATE_HOME") "~/.local/var/lib/"))
+ (_ (error "Unknown XDG directory type: %S" type))))))
+ (when (and create (not (file-accessible-directory-p dir)))
+ (make-directory dir t))
+ dir))
+
+;; Global/built-in Custom settings
+;; Apply these as early as possible so that e.g. the native-comp files go to the right place.
+(mapc (apply-partially #'apply #'customize-set-variable)
+ `((native-comp-async-report-warnings-errors silent "Don't pop up Warnings buffer for native compilation.")
+ ;; Emacs GUI customization.
+ (inhibit-startup-screen t "Don't show the startup screen with help links.")
+ (menu-bar-mode nil "Hide the menu bar globally.")
+ (tool-bar-mode nil "Hide the tool bar globally.")
+ (tooltip-mode nil "Show tooltips in the echo area instead.")
+ (max-mini-window-height 3 "Let the echo area grow to a maximum of 3 lines, e.g. when using `eldoc-mode'.")
+ (scroll-up-aggressively 0.0 "Don't recenter the window if the point moves off the page.")
+ (scroll-down-aggressively 0.0 "Don't recenter the window if the point moves off the page.")
+ (pixel-scroll-precision-mode t "Enable pixel-by-pixel scrolling, e.g. to handle inline images.")
+ ;; Niceties.
+ (tramp-default-method "scpx" "ssh and scp hang forever. scpx is faster than sshx for large files.")
+ (global-hl-line-mode t "Highlight the current line in all buffers.")
+ (indicate-empty-lines t "Show a little marker in the margin for lines past EOF.")
+ (column-number-mode t "Show the column number in the statusline.")
+ (electric-pair-mode t "Auto-pair suitable characters like parentheses.")
+ (tab-always-indent complete "Enable completion-on-tab.")
+ (completion-cycle-threshold 6 "Allow cycling through completions if there are 6 or fewer of them.")
+ (completion-styles (basic partial-completion) "Enable fast completion styles.")
+ (shell-kill-buffer-on-exit t "Kill *shell* buffers as soon as their shell session exits.")
+ ;; Indentation, formatting.
+ (indent-tabs-mode nil "Always use spaces to indent.")
+ (sentence-end-double-space nil "Use a single space after a sentence.")
+ (fill-column 78 "Make hard-wrapped text a bit wider.")
+ (require-final-newline t "Always add a final newline on save, if there is none.")
+ ;; Make Emacs a good Guix citizen.
+ (package-archives nil "Don't fetch packages from the internet; only get them from Guix.")
+ ;; Default mode.
+ (major-mode text-mode "Use `text-mode' by default in new buffers, not `fundamental-mode'.")
+ ;; Recent files and history. Keep them out of ~/.config.
+ (package-user-dir ,(tw/xdg-emacs-subdir 'data "elpa") "Save ELPA-related files here.")
+ (auto-save-list-file-prefix ,(tw/xdg-emacs-subdir 'data "auto-save-list/saves-") "Put auto-save lists here.")
+ (make-backup-files nil "Don't create backup files. They're annoying.")
+ (backup-directory-alist (("." . ,(tw/xdg-emacs-subdir 'data "backup"))) "Put backup files in a sensible place.")
+ (backup-by-copying t "Avoid breaking hardlinks when making backup files.")
+ (auto-save-file-name-transforms
+ ;; `file-name-as-directory' is important, since Emacs takes the directory part when UNIQUIFY is t.
+ ((".*" ,(file-name-as-directory (tw/xdg-emacs-subdir 'data "auto-save" t)) t))
+ "Put auto-save #files# in a sensible directory.")
+ (recentf-max-saved-items 1000 "Save lots of recently-opened files.")
+ (recentf-save-file ,(tw/xdg-emacs-subdir 'data "recentf.el") "Save recently-opened files here.")
+ (recentf-mode t "Save recently-opened files.")
+ (savehist-file ,(tw/xdg-emacs-subdir 'data "savehist.el") "Save minibuffer history here.")
+ (savehist-mode t "Save minibuffer history on quit.")))
+
+(defalias 'yes-or-no-p #'y-or-n-p
+ "Always use `y-or-n-p' when asking for confirmation.")
+
+;; Custom modes depending on file names.
+(mapc (apply-partially #'add-to-list 'auto-mode-alist)
+ `((,(rx (or bos "/") "PKGBUILD" eos) . bash-ts-mode)
+ (,(rx ".install" eos) . bash-ts-mode)
+ (,(rx (or bos "/") "COMMIT_EDITMSG" eos) . diff-mode) ; useful for `git commit -v'
+ (,(rx bos "/tmp/neomutt-") . mail-mode)
+ (,(rx ".eml" eos) . mail-mode)
+ (,(rx "." (1+ anything) "rc" eos) . conf-unix-mode)))
+
+(add-to-list 'magic-mode-alist
+ `(,(rx "#!" (* (not space))
+ (? "env" (+ space) (? "-S" (+ space)))
+ (or "guile" "racket"))
+ . scheme-mode))
+
+(add-hook 'mail-mode-hook #'auto-fill-mode)
+
+(defun tw/show-trailing-whitespace ()
+ "Highlight trailing spaces in the current buffer."
+ (setq-local show-trailing-whitespace t))
+
+(mapc (lambda (hook)
+ (add-hook hook #'tw/show-trailing-whitespace))
+ '(prog-mode-hook conf-mode-hook yaml-mode-hook alidist-mode-hook))
+
+(defun tw/enable-word-wrap ()
+ "Enable word wrapping."
+ (toggle-word-wrap +1))
+(add-hook 'markdown-mode-hook #'tw/enable-word-wrap)
+(add-hook 'org-mode-hook #'tw/enable-word-wrap)
+
+;; `use-package' requirements.
+(require 'package)
+(package-initialize t)
+(eval-when-compile
+ (require 'use-package))
+(use-package diminish) ; for using :diminish later
+(use-package bind-key) ; for using :bind later
+
+;; Some packages below have `:commands (...) :demand t'.
+;; We need :commands for the byte-compiler, but we want to load the package immediately.
+
+(use-package gcmh ; "garbage collector magic hack": run GC when not in focus
+ :config (gcmh-mode +1)
+ :diminish gcmh-mode)
+
+;; Look and feel
+(set-face-attribute 'default nil :family "Hermit" :height 100)
+;; For some reason, Emacs doesn't detect italic support, and falls back to
+;; underlining. Stop it from doing this and use italics instead.
+(set-face-attribute 'italic nil :slant 'italic :underline nil)
+
+(use-package catppuccin-theme
+ :load-path "./"
+ :custom
+ (catppuccin-flavor 'mocha "Use the darkest Catppuccin theme.")
+ (catppuccin-italic-comments t "Make comments italic. It looks nicer.")
+ (catppuccin-italic-variables t "Make variable names italic. It looks nicer.")
+ :config (catppuccin-reload))
+
+(use-package smart-mode-line
+ :hook (after-init . sml/setup)
+ :custom
+ (sml/no-confirm-load-theme t "Stop Emacs from constantly asking for user confirmation.")
+ (sml/mode-width 'right "Move the minor-mode list to the right of the modeline.")
+ (sml/theme 'respectful "Make `smart-mode-line' blend in with the active theme."))
+
+;; General editor behaviour.
+;; TODO: Move from ivy + counsel to vertico + orderless + consult + marginalia
+;; (+ embark?), to integrate better with vanilla Emacs and `completing-read'.
+;; https://github.com/minad/vertico -- light completion engine
+;; https://github.com/minad/vertico#child-frames-and-popups
+;; https://github.com/minad/vertico#complementary-packages
+;; https://github.com/minad/marginalia -- docstrings in M-x menu
+;; https://github.com/oantolin/orderless -- regex search for vertico
+;; https://github.com/minad/consult -- collection of commands using vertico
+;; https://github.com/oantolin/embark -- make vertico better depending on thing at point
+
+(use-package ivy
+ :commands (ivy-mode) :demand t
+ :custom
+ (ivy-use-selectable-prompt t "Allow selecting the ivy input as-is.")
+ (ivy-use-virtual-buffers t "Show recentf and bookmarks in buffers list.")
+ :config (ivy-mode +1)
+ :diminish ivy-mode)
+
+(use-package counsel ; extra niceties for `ivy-mode'
+ :after (ivy evil) ; evil for :bind-ing to <leader>
+ :bind (("<leader>SPC" . counsel-M-x) ; <leader><leader> doesn't work
+ ("<leader>fr" . counsel-buffer-or-recentf)
+ :map evil-visual-state-map
+ ("<leader>SPC" . counsel-M-x))
+ :commands (counsel-mode) :demand t
+ :config (counsel-mode +1)
+ :diminish counsel-mode)
+
+(use-package dash-docs
+ :custom
+ (dash-docs-docsets-path
+ (file-name-as-directory (tw/xdg-emacs-subdir 'data "dash-docsets" t))
+ "Store docsets in the XDG data directory.")
+ (dash-docs-browser-func 'eww "Open documentation pages using `eww' instead of an external browser.")
+ (dash-docs-enable-debugging nil "Disable popping up useless warnings."))
+
+(defun tw/counsel-dash-is-help ()
+ "Install `counsel-dash-at-point' as `evil-lookup-func'."
+ ;; Note: `evil-lookup-func' is already set to something else by
+ ;; `tw/help-is-eldoc' for `eglot-mode'.
+ (setq-local evil-lookup-func #'counsel-dash-at-point
+ counsel-dash-docsets
+ (cl-case major-mode
+ (lisp-mode '("Common Lisp"))
+ ((python-mode python-ts-mode) '("Python 3"))
+ (c++-mode '("C++"))
+ (cmake-mode '("CMake"))
+ (puppet-mode '("Puppet"))
+ (yaml-mode '("Ansible"))
+ (tcl-mode '("Tcl"))
+ (html-mode '("HTML" "CSS"))
+ ((css-mode css-ts-mode) '("CSS"))
+ (web-mode '("HTML" "CSS")))))
+
+(use-package counsel-dash
+ :after (dash-docs which-key)
+ :commands (counsel-dash-at-point) :demand t
+ :init (which-key-add-key-based-replacements
+ "<leader>d" '("docs" . "Documentation"))
+ :bind (("<leader>K" . counsel-dash-at-point)
+ ("<leader>dK" . counsel-dash)
+ ("<leader>di" . counsel-dash-install-docset)
+ ("<leader>da" . counsel-dash-activate-docset)
+ ("<leader>dd" . counsel-dash-deactivate-docset))
+ :hook (( lisp-mode python-mode python-ts-mode cmake-mode c++-mode puppet-mode yaml-mode
+ tcl-mode html-mode css-mode css-ts-mode web-mode)
+ . tw/counsel-dash-is-help)
+ :config
+ ;; Activate all installed docsets by default.
+ (setq counsel-dash-common-docsets (dash-docs-installed-docsets)))
+
+(use-package rainbow-mode
+ :after (evil)
+ :bind (("<leader>tR" . rainbow-mode)))
+
+(use-package form-feed
+ :commands (global-form-feed-mode) :demand t
+ :config (global-form-feed-mode +1)
+ :diminish form-feed-mode)
+
+(use-package display-line-numbers
+ ;; Included in Emacs >= 26. Better than `linum-mode'.
+ ;; There is also `global-display-line-numbers-mode', but that also
+ ;; enables line numbers in help windows, which I don't want.
+ :hook (prog-mode conf-mode yaml-mode alidist-mode))
+
+(use-package which-key
+ :commands (which-key-mode) :demand t
+ :config (which-key-mode +1)
+ :diminish which-key-mode)
+
+(use-package undo-tree
+ :after (evil) ; for our :bind-ing
+ :bind (("<leader>U" . undo-tree-visualize))
+ :custom
+ (undo-tree-history-directory-alist
+ `(("." . ,(file-name-as-directory (tw/xdg-emacs-subdir 'data "undo-tree-history"))))
+ "Store all `undo-tree' history in a single directory, instead of next to the associated file.")
+ :commands (global-undo-tree-mode)
+ :demand t ; this is required so that the :config stanza is actually run asap despite :bind
+ :config (global-undo-tree-mode +1)
+ :diminish undo-tree-mode)
+
+;; IDE-like features.
+(use-package project
+ :after (which-key evil)
+ :init
+ (which-key-add-key-based-replacements
+ "<leader>p" '("project" . "Project"))
+ (evil-define-key '(normal visual) 'global
+ (kbd "<leader>fp") #'project-find-file) ; also <leader>pf
+ :bind-keymap ("<leader>p" . project-prefix-map))
+
+(use-package vc
+ :after (which-key evil)
+ :init (which-key-add-key-based-replacements
+ "<leader>v" '("vc" . "Version control")
+ "<leader>vM" '("merge" . "Version control merging"))
+ :bind-keymap ("<leader>v" . vc-prefix-map))
+
+(use-package log-edit
+ :after (evil vc)
+ :config
+ (evil-define-key '(normal visual) log-edit-mode-map
+ (kbd "<localleader>\\") #'log-edit-done
+ (kbd "<localleader>a") #'log-edit-insert-changelog
+ (kbd "<localleader>d") #'log-edit-show-diff
+ (kbd "<localleader>f") #'log-edit-show-files
+ (kbd "<localleader>k") #'log-edit-kill-buffer
+ (kbd "<localleader>w") #'log-edit-generate-changelog-from-diff))
+
+(use-package company
+ :config (global-company-mode +1)
+ :diminish company-mode)
+
+(use-package company-quickhelp
+ :after (company)
+ :config (company-quickhelp-mode +1)
+ :diminish company-quickhelp-mode)
+
+(use-package company-posframe
+ :after (company)
+ :config (company-posframe-mode +1)
+ :diminish company-posframe-mode)
+
+(use-package flyspell
+ :hook mail-mode)
+
+(use-package flymake
+ :after (evil which-key)
+ :demand t ; needed for `flymake-collection'
+ :hook (prog-mode yaml-mode alidist-mode)
+ :init (which-key-add-key-based-replacements
+ "<leader>e" '("errors" . "Flymake"))
+ :bind (("<leader>eb" . flymake-start)
+ ("<leader>ec" . display-local-help) ; Show the error message at point in the minibuffer.
+ ; `flymake' also shows it using `eldoc', but documentation
+ ; seems to override error messages.
+ ; `flymake-show-diagnostic' only says "Nothing at point".
+ ("<leader>el" . flymake-show-buffer-diagnostics)
+ ("<leader>ep" . flymake-show-project-diagnostics)
+ ("<leader>en" . flymake-goto-next-error)
+ ("<leader>eN" . flymake-goto-prev-error)
+ ("<leader>ev" . flymake-running-backends)
+ ("<leader>eV" . flymake-disabled-backends))
+ :custom
+ (flymake-suppress-zero-counters nil "Show severity counters even when they are zero."))
+
+(use-package flymake-collection
+ :after (flymake)
+ :demand t ; we need it loaded now
+ ;; This needs to be called in `after-init-hook' so that all other
+ ;; packages' `:flymake-hook's are processed before f-c-hook-setup is
+ ;; called. See https://github.com/mohkale/flymake-collection.
+ :hook (after-init . flymake-collection-hook-setup))
+
+;; Language Server Protocol.
+(defun tw/help-is-eldoc (&rest _)
+ "Set up `evil-lookup-func' to display the `eldoc' buffer."
+ (when (eglot-managed-p)
+ (setq-local evil-lookup-func #'eldoc-doc-buffer)))
+
+(use-package eglot
+ ;; I have clang (for clangd) and python-lsp-server installed.
+ ;; `:hook' adds `-mode' to the package name, but `eglot-mode' doesn't exist.
+ :hook (((python-mode python-ts-mode c-mode c++-mode c-or-c++-ts-mode) . eglot-ensure)
+ (eglot-managed-mode . tw/help-is-eldoc))
+ :commands (eglot)
+ :functions (eglot-managed-p)
+ :custom
+ (eglot-autoshutdown t "Shut down language servers after deleting their last associated buffer.")
+ (eglot-sync-connect 0.1 "Wait for the language server in the background if it takes longer than 100ms."))
+
+;; Tree-sitter
+;; TODO: Try any/all of the following new tree-sitter-based major modes.
+;; Enable them using the following, replacing the relevant "old" major mode:
+;; (add-to-list 'major-mode-remap-alist '(ruby-mode . ruby-ts-mode))
+;; New major mode 'css-ts-mode'.
+;; New major mode 'dockerfile-ts-mode'.
+;; New major mode 'ruby-ts-mode'.
+
+(mapc (lambda (dir)
+ (add-to-list 'treesit-extra-load-path (file-name-as-directory (expand-file-name dir))))
+ '("/run/current-system/profile/lib/tree-sitter"
+ "~/.guix-home/profile/lib/tree-sitter"
+ "~/.guix-profile/lib/tree-sitter"))
+
+(use-package treesit
+ :custom
+ (treesit-font-lock-level 4 "Enable Angry Fruit Salad mode."))
+
+;; Non-LSP language modes.
+(use-package c-ts-mode
+ :init
+ (add-to-list 'major-mode-remap-alist '(c-mode . c-ts-mode))
+ (add-to-list 'major-mode-remap-alist '(c++-mode . c++-ts-mode))
+ (add-to-list 'major-mode-remap-alist '(c-or-c++-mode . c-or-c++-ts-mode)))
+
+(use-package cmake-ts-mode
+ :mode (rx (or (: (or bos "/") "CMakeLists.txt") ".cmake") eos))
+
+(use-package json-ts-mode
+ :mode (rx ".json" eos)
+ :config
+ (evil-define-key '(normal visual) json-ts-mode-map
+ (kbd "<localleader>==") #'json-pretty-print
+ (kbd "<localleader>=b") #'json-pretty-print-buffer
+ (kbd "<localleader>=o") #'json-pretty-print-ordered
+ (kbd "<localleader>=B") #'json-pretty-print-buffer-ordered))
+
+(use-package gnuplot
+ :commands (gnuplot-mode gnuplot-make-buffer)
+ :mode ((rx ".gnuplot" eos) . gnuplot-mode))
+
+(use-package graphviz-dot-mode
+ :mode (rx ".dot" eos)
+ :custom (graphviz-dot-view-command "xdot %s" "Use xdot for previewing graphviz files."))
+
+(use-package haskell-mode
+ :mode (rx (or ".hs" ".lhs" ".hsc" ".cpphs" ".c2hs") eos))
+
+(use-package hcl-mode
+ :mode (rx "." (or "hcl" "nomad") eos))
+
+(use-package mmm-mode
+ :commands (mmm-mode)
+ ;; Don't highlight submodes specially at all. The default background is annoying.
+ :custom-face (mmm-default-submode-face ((t (:background unspecified)))))
+
+(use-package puppet-mode
+ :mode (rx ".pp" eos))
+
+(use-package python
+ :after (flymake-collection)
+ :commands (python-mode python-ts-mode)
+ :mode (((rx ".py" (? (or ?\i ?\w)) eos) . python-ts-mode)
+ ((rx ".aurora" eos) . python-ts-mode))
+ :config
+ ;; Disable all flymake-collection linters in Python modes, since eglot/pylsp
+ ;; should take care of it. It doesn't do type checking, so enable mypy.
+ (cl-dolist (mode '(python-ts-mode python-mode))
+ (add-to-list 'flymake-collection-config `(,mode flymake-mypy))))
+
+(use-package rec-mode
+ :mode (rx ".rec" eos))
+
+(use-package sh-script ; built-in
+ ;; Use `bash-ts-mode' instead of `sh-mode' if possible.
+ ;; `bash-ts-mode' falls back to `sh-mode' if necessary.
+ ;; Manually configuring :mode etc would be annoying, since there are a lot of entries.
+ :config (add-to-list 'major-mode-remap-alist '(sh-mode . bash-ts-mode))
+ :custom (sh-basic-offset 2 "Use 2 spaces for `sh-mode' indents."))
+
+(use-package tcl
+ :mode ((rx ".tcl" eos) . tcl-mode)
+ :magic ((rx "#%Module1.0") . tcl-mode))
+
+(use-package web-mode
+ :mode (rx "." (or "htm" "html" "js" "css" "scss") eos)
+ :custom
+ (web-mode-css-indent-offset 2 "Indent CSS by two spaces."))
+
+(use-package yaml-mode
+ :mode (rx (or (seq ".y" (? "a") "ml")
+ (seq "aliPublish" (* (not ?/)) ".conf"))
+ eos))
+
+(defun tw/ledger-format-on-save ()
+ "Re-indent the entire file."
+ ;; Subset of `ledger-mode-clean-buffer'. That also sorts the buffer, which I don't want.
+ (save-excursion
+ (let ((start (point-min-marker))
+ (end (point-max-marker)))
+ (untabify start end)
+ (ledger-post-align-postings start end)
+ (ledger-mode-remove-extra-lines))))
+
+(defun tw/enable-ledger-format-on-save ()
+ "Enable reformating the open file on save."
+ (add-hook 'before-save-hook #'tw/ledger-format-on-save 0 t))
+
+(use-package ledger-mode
+ :after (evil)
+ :commands (ledger-mode)
+ :mode (rx ".journal" eos)
+ :hook (ledger-mode . tw/enable-ledger-format-on-save)
+ :custom
+ (ledger-default-date-format ledger-iso-date-format "Use hledger-style dates.")
+ (ledger-reconcile-default-date-format ledger-iso-date-format "Use hledger-style dates.")
+ (ledger-reconcile-default-commodity "€" "Make euros the default currency.")
+ (ledger-post-account-alignment-column 2 "Use 2-space indents.")
+ (ledger-post-amount-alignment-at :decimal "Align amounts at decimal points/commas.")
+ (ledger-post-amount-alignment-column 52 "Align amounts' decimal points to the 52nd column.")
+ (ledger-highlight-xact-under-point nil "Don't highlight the transaction at point.")
+ :config
+ (evil-define-key 'normal ledger-mode-map
+ (kbd "TAB") #'ledger-indent-line))
+
+(use-package lisp
+ :init (which-key-add-key-based-replacements
+ "<leader>k" '("sexp-nav" . "S-expression navigation"))
+ :bind (("<leader>kl" . forward-sexp)
+ ("<leader>kh" . backward-sexp)
+ ("<leader>kL" . forward-list)
+ ("<leader>kH" . backward-list)
+ ("<leader>kj" . down-list)
+ ("<leader>kk" . up-list)
+ ("<leader>kK" . backward-up-list)
+ ("<leader>kd" . kill-sexp)
+ ("<leader>kD" . backward-kill-sexp)
+ ("<leader>kb" . beginning-of-defun)
+ ("<leader>kB" . beginning-of-defun-comments)
+ ("<leader>ke" . end-of-defun)
+ ("<leader>kv" . mark-sexp)
+ ("<leader>kV" . mark-defun)
+ ("<leader>kN" . narrow-to-defun)
+ ("<leader>ks" . insert-pair)
+ ("<leader>kr" . raise-sexp)
+ ("<leader>kc" . check-parens)))
+
+(defun tw/resize-repl-window ()
+ "Make the REPL window small, so it stays out of the way."
+ (shrink-window (- (window-height) 5)))
+
+(use-package geiser
+ :after (evil)
+ :commands (geiser
+ geiser-eval-buffer geiser-eval-definition geiser-eval-region
+ geiser-eval-last-sexp geiser-mode-switch-to-repl
+ geiser-mode-switch-to-repl-and-enter)
+ :hook ((scheme-mode . geiser-autodoc-mode)
+ (geiser-repl-mode . tw/resize-repl-window))
+ :config
+ (evil-define-key '(normal visual) scheme-mode-map
+ (kbd "<localleader>z") #'geiser-mode-switch-to-repl
+ (kbd "<localleader>Z") #'geiser-mode-switch-to-repl-and-enter
+ (kbd "<localleader>eb") #'geiser-eval-buffer
+ (kbd "<localleader>ef") #'geiser-eval-definition
+ (kbd "<localleader>er") #'geiser-eval-region
+ (kbd "<localleader>el") #'geiser-eval-last-sexp)
+ :defines scheme-mode-map)
+
+(use-package geiser-guile
+ :after (geiser))
+
+(use-package sly
+ :after (evil)
+ :hook ((lisp-mode . sly-mode) ; `common-lisp-mode' is `lisp-mode'.
+ (sly-mrepl-mode . tw/resize-repl-window))
+ :config
+ (evil-define-key '(normal visual) lisp-mode-map
+ (kbd "<localleader>C-c") #'sly-interrupt
+ (kbd "<localleader>z") #'sly
+ (kbd "<localleader>Z") #'sly-mrepl-sync
+ (kbd "<localleader>i") #'sly-inspect
+ (kbd "<localleader>D") #'sly-disassemble-symbol
+ (kbd "<localleader>E") #'sly-edit-value
+ (kbd "<localleader>eT") #'sly-list-threads ; eval requests get a new thread each
+ (kbd "<localleader>e:") #'sly-interactive-eval
+ (kbd "<localleader>el") #'sly-eval-last-expression
+ (kbd "<localleader>ep") #'sly-pprint-eval-last-expression
+ (kbd "<localleader>eb") #'sly-eval-buffer
+ (kbd "<localleader>ef") #'sly-eval-defun
+ (kbd "<localleader>er") #'sly-eval-region
+ (kbd "<localleader>eF") #'sly-compile-defun
+ (kbd "<localleader>eB") #'sly-compile-file
+ (kbd "<localleader>eL") #'sly-compile-and-load-file
+ (kbd "<localleader>eR") #'sly-compile-region
+ (kbd "<localleader>eU") #'sly-undefine-function
+ (kbd "<localleader>eM") #'sly-remove-method
+ (kbd "<localleader>dd") #'sly-describe-symbol
+ (kbd "<localleader>df") #'sly-describe-function
+ (kbd "<localleader>da") #'sly-apropos
+ (kbd "<localleader>dA") #'sly-apropos-all
+ (kbd "<localleader>dg") #'sly-edit-definition
+ (kbd "<localleader>dC-o") #'sly-pop-find-definition-stack
+ (kbd "<localleader>dG") #'sly-edit-uses
+ (kbd "<localleader>dwc") #'sly-who-calls
+ (kbd "<localleader>dwC") #'sly-calls-who
+ (kbd "<localleader>dwr") #'sly-who-references
+ (kbd "<localleader>dwb") #'sly-who-binds
+ (kbd "<localleader>dws") #'sly-who-sets
+ (kbd "<localleader>dwm") #'sly-who-macroexpands
+ (kbd "<localleader>dwS") #'sly-who-specializes
+ (kbd "<localleader>dhs") #'hyperspec-lookup ; hyperspec.el is bundled with sly; opens in browser
+ (kbd "<localleader>dhf") #'hyperspec-lookup-format
+ (kbd "<localleader>dhm") #'hyperspec-lookup-reader-macro
+ (kbd "<localleader>cl") #'sly-list-connections
+ (kbd "<localleader>cn") #'sly-next-connection
+ (kbd "<localleader>cp") #'sly-prev-connection
+ (kbd "<localleader>m1") #'sly-expand-1
+ (kbd "<localleader>mm") #'sly-macroexpand-all
+ (kbd "<localleader>mf") #'sly-format-string-expand
+ (kbd "<localleader>tt") #'sly-trace-dialog-toggle-trace
+ (kbd "<localleader>ts") #'sly-trace-dialog
+ (kbd "<localleader>tf") #'sly-toggle-trace-fdefinition
+ (kbd "<localleader>tF") #'sly-untrace-all
+ (kbd "<localleader>ss") #'sly-stickers-dwim ; an ephemeral `print' around the thing at point
+ (kbd "<localleader>sr") #'sly-stickers-replay
+ (kbd "<localleader>sb") #'sly-stickers-toggle-break-on-stickers
+ (kbd "<localleader>sf") #'sly-stickers-fetch
+ (kbd "<localleader>sn") #'sly-stickers-next-sticker
+ (kbd "<localleader>sp") #'sly-stickers-prev-sticker
+ (kbd "<localleader>ta") #'sly-autodoc-mode)
+ :custom
+ (sly-mrepl-history-file-name (tw/xdg-emacs-subdir 'data "sly-mrepl-history")))
+
+;; Org-mode
+(use-package org
+ :commands (org-mode)
+ :mode ((rx ".org" eos) . org-mode)
+ :custom
+ (org-latex-src-block-backend 'minted "Colourise source code.")
+ (org-latex-packages-alist
+ '(("" "svg")
+ ("" "minted"))
+ "Use svg and syntax highlighting packages.")
+ (org-latex-pdf-process
+ '("latexmk -shell-escape -f -pdf -%latex -interaction=nonstopmode -output-directory=%o %f")
+ "Allow -shell-escape needed by svg and minted packages."))
+
+(use-package ob ; org-babel
+ :after (org)
+ :custom
+ (org-confirm-babel-evaluate nil "Allow running code blocks without confirmation.")
+ ;; List of supported languages:
+ ;; https://orgmode.org/worg/org-contrib/babel/languages/index.html
+ (org-babel-load-languages
+ '((emacs-lisp . t)
+ (lisp . t)
+ (dot . t)
+ (python . t)
+ (gnuplot . t)
+ (rec . t)) ; see `ob-rec' below
+ "Load bindings for more languages for use in #+begin_src blocks."))
+
+(defun tw/latex-section-commands (name)
+ "Create a pair of section commands like (\"\\NAME{%s}\" . \"\\NAME*{%s}\").
+For use in `org-latex-classes'."
+ (cons (format "\\%s{%%s}" name) (format "\\%s*{%%s}" name)))
+(defconst tw/latex-part (tw/latex-section-commands "part")
+ "Part LaTeX commands for `org-latex-classes'.")
+(defconst tw/latex-chapter (tw/latex-section-commands "chapter")
+ "Chapter LaTeX commands for `org-latex-classes'.")
+(defconst tw/latex-section-and-below
+ (mapcar #'tw/latex-section-commands
+ '("section" "subsection" "subsubsection" "paragraph" "subparagraph"))
+ "Section to subparagraph LaTeX commands for `org-latex-classes'.")
+
+(use-package ox-latex ; org-export-latex
+ :after (org)
+ :custom
+ (org-latex-classes
+ `(("paperlike" "\\documentclass{paperlike}" . ,tw/latex-section-and-below)
+ ("examtext" "\\documentclass{examtext}" . ,tw/latex-section-and-below)
+ ("minutes" "\\documentclass{minutes}" . ,tw/latex-section-and-below)
+ ("mapreport" "\\documentclass{mapreport}" ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("pt3report" "\\documentclass{pt3report}" ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("article" "\\documentclass{article}" . ,tw/latex-section-and-below)
+ ("scrartcl" "\\documentclass{scrartcl}" . ,tw/latex-section-and-below)
+ ("report" "\\documentclass{report}" ,tw/latex-part ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("report-noparts" "\\documentclass{report}" ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("book" "\\documentclass{book}" ,tw/latex-part ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("book-noparts" "\\documentclass{book}" ,tw/latex-chapter . ,tw/latex-section-and-below)
+ ("checklist" "\\documentclass{checklist}" . ,tw/latex-section-and-below))
+ "Define more documentclasses for org-latex."))
+
+(use-package outline
+ :commands (outline-mode outline-minor-mode)
+ :custom
+ ;; Mirror the default "C-c @" binding for `outline-minor-mode'.
+ (outline-minor-mode-prefix (kbd "<localleader>@") "Use localleader for `outline-minor-mode' keybindings."))
+
+;; My own custom packages, and stuff that isn't on MELPA.
+(use-package actionlint
+ :after (flymake)
+ :load-path "include/"
+ :hook ((yaml-mode yaml-ts-mode) . actionlint-setup))
+
+(use-package alidist-mode
+ :after (flymake)
+ :load-path "include/"
+ :commands (alidist-mode)
+ :mode (rx (or bot "/") "alidist/" (1+ (not ?\/)) ".sh" eot))
+
+(use-package flymake-guile
+ :after (flymake)
+ :load-path "include/"
+ :hook (scheme-mode . flymake-guile-enable))
+
+(use-package bemscript-mode
+ :load-path "include/"
+ :mode (rx ".bem" eos))
+
+(use-package ifm-mode
+ :load-path "include/"
+ :mode (rx ".ifm" eos))
+
+(use-package pam-env-mode
+ :load-path "include/"
+ :mode (rx (or bos "/") (or "pam_env.conf" ".pam_environment") eos))
+
+(use-package environmentd-mode
+ :load-path "include/"
+ :mode (rx (or bos "/")
+ (or (: (? "etc/") "environment")
+ (: ".environment.d/" (1+ (not ?\/)) ".conf"))
+ eos))
+
+(use-package ob-rec
+ ;; `org-babel' hooks for `rec-mode'
+ :after (org ob rec-mode)
+ :load-path "include/")
+
+(use-package vcard-mode
+ :load-path "include/"
+ :mode (rx "." (or "vcf" "vcard") eos))
+
+;; Vim keybindings.
+(defun tw/switch-to-other-buffer ()
+ "Switch to the last-used buffer."
+ (interactive)
+ (switch-to-buffer (other-buffer)))
+
+(defun tw/new-buffer ()
+ "Open a new, empty buffer."
+ (interactive)
+ (switch-to-buffer (generate-new-buffer "untitled")))
+
+(defun tw/delete-current-buffer-file ()
+ "Ask for confirmation, then delete the file associated with the current buffer."
+ (interactive)
+ (let ((buffer (current-buffer)))
+ (when (yes-or-no-p (concat "Delete `" (buffer-file-name buffer) "'?"))
+ (delete-file (buffer-file-name buffer))
+ (kill-buffer buffer))))
+
+(use-package evil
+ :after (which-key)
+ :commands (evil-mode evil-ex-nohighlight)
+ :init (setq evil-want-keybinding nil) ; evil-collection needs this
+ :custom
+ (evil-echo-state nil "Don't show the '--- INSERT ---' string in the echo area on evil state changes.")
+ (evil-undo-system 'undo-tree "Use `undo-tree' for evil's undo-redo function.")
+ (evil-search-module 'evil-search "Use evil's built-in search function, for search history support.")
+ (evil-want-minibuffer t "Use evil bindings in the minibuffer too.")
+ (evil-want-C-u-scroll t "Scroll on C-u in normal mode, not `universal-argument'.")
+ (evil-want-C-u-delete t "Delete line on C-u in insert mode, not `universal-argument'.")
+ (evil-want-Y-yank-to-eol t "Yank from point to end-of-line on Y.")
+ (evil-symbol-word-search t "Always search by full variable names when using * and #.")
+ :config
+ (evil-mode +1)
+ (evil-set-leader '(normal visual) (kbd "SPC")) ; <leader>
+ (evil-set-leader '(normal visual) (kbd "\\") t) ; <localleader>
+ (evil-define-key '(normal motion) diff-mode-shared-map ; not `diff-mode-map', else toggling `read-only-mode' destroys the binding
+ (kbd "<localleader>\\") #'read-only-mode) ; mirror default binding from evil-collection
+ (evil-define-key '(normal insert visual replace) 'global
+ (kbd "C-s") #'save-buffer)
+ ;; Global major-mode-independent keys should be defined here. Major
+ ;; mode-dependent keys (e.g. for launching a REPL) should go under
+ ;; <localleader> instead. Use `use-package' `:bind' for those.
+ (evil-define-key '(normal visual) 'global
+ ;; These keybindings mirror the default Spacemacs ones because I have
+ ;; muscle memory of those.
+ (kbd "<leader>:") #'eval-expression
+ (kbd "<leader>TAB") #'tw/switch-to-other-buffer
+ (kbd "<leader>bb") #'switch-to-buffer
+ (kbd "<leader>bd") #'kill-current-buffer
+ (kbd "<leader>bn") #'tw/new-buffer
+ (kbd "<leader>br") #'revert-buffer-quick
+ (kbd "<leader>bs") #'scratch-buffer
+ (kbd "<leader>bw") #'read-only-mode
+ (kbd "<leader>bx") #'kill-buffer-and-window
+ (kbd "<leader>fd") #'tw/delete-current-buffer-file
+ (kbd "<leader>ff") #'find-file
+ (kbd "<leader>fR") #'rename-visited-file
+ (kbd "<leader>fs") #'save-buffer
+ (kbd "<leader>h") help-map
+ (kbd "<leader>hw") #'which-key-show-top-level
+ (kbd "<leader>sc") #'evil-ex-nohighlight
+ (kbd "<leader>td") #'toggle-debug-on-error
+ (kbd "<leader>tf") #'auto-fill-mode
+ (kbd "<leader>tl") #'toggle-truncate-lines
+ (kbd "<leader>tn") #'display-line-numbers-mode
+ (kbd "<leader>u") #'universal-argument
+ (kbd "<leader>w") evil-window-map
+ (kbd "<leader>wd") #'evil-window-delete ; analogous to "<leader>bd"
+ (kbd "<leader>wx") #'kill-buffer-and-window) ; analogous to "<leader>bx"
+ (which-key-add-key-based-replacements
+ ;; Names are a `cons' of a short name and a long name.
+ ;; E.g. for <leader>b, "buffer" is shown under "b" in the "<leader>" menu,
+ ;; while "Buffers" is shown as the title in the "<leader>b" menu.
+ "<leader>b" '("buffer" . "Buffers")
+ "<leader>f" '("file" . "Files")
+ "<leader>h" '("help" . "General help and documentation")
+ "<leader>q" '("quit" . "Finish editing the current buffer in emacsclient")
+ "<leader>s" '("search" . "Search operations and options")
+ "<leader>t" '("toggle" . "Toggles and quick settings")
+ "<leader>w" '("window" . "Windows"))
+ :functions (evil-define-key evil-set-leader
+ evil-define-key* evil-window-delete evil-delay)
+ :defines (evil-visual-state-map))
+
+(use-package evil-collection
+ :after (evil)
+ :commands (evil-collection-init) :demand t
+ :config (evil-collection-init)
+ :diminish evil-collection-unimpaired-mode
+ :custom
+ ;; Without `evil-collection-key-blacklist', in `diff-mode', space isn't
+ ;; assigned to the leader key automatically, unlike in other modes.
+ (evil-collection-key-blacklist '("SPC" "\\") "Don't bind to our leader keys at all.")
+ (evil-collection-setup-minibuffer t "Use evil-collection in minibuffer to match `evil-want-minibuffer'."))
+
+(use-package evil-org
+ :after (evil org)
+ :hook org-mode
+ :config
+ (evil-define-key '(normal visual) org-mode-map
+ (kbd "<localleader>\\") #'org-ctrl-c-ctrl-c
+ (kbd "<localleader>ib") #'org-insert-structure-template
+ (kbd "<localleader>id") #'org-insert-drawer
+ (kbd "<localleader>iD") #'org-insert-time-stamp
+ (kbd "<localleader>ih") #'org-insert-heading
+ (kbd "<localleader>iH") #'org-insert-subheading
+ (kbd "<localleader>it") #'org-insert-todo-heading
+ (kbd "<localleader>iT") #'org-insert-todo-subheading
+ (kbd "<localleader>ii") #'org-insert-item
+ (kbd "<localleader>il") #'org-insert-link
+ (kbd "<localleader>p") #'org-set-property
+ (kbd "<localleader>t") #'org-set-tags
+ ;; Source code block editing
+ (kbd "<localleader>'") #'org-edit-src-code
+ (kbd "<localleader>e") #'org-export-dispatch)
+ (evil-define-key '(normal visual) org-src-mode-map
+ (kbd "<localleader>'") #'org-edit-src-exit
+ (kbd "<localleader>\\") #'org-edit-src-save
+ (kbd "<localleader>a") #'org-edit-src-abort))
+
+(use-package evil-replace-with-register
+ :after (evil)
+ :commands (evil-replace-with-register-install) :demand t
+ ;; :custom (evil-replace-with-register-key "gR" "Use the default key.")
+ :config (evil-replace-with-register-install))
+
+(use-package evil-commentary ; e.g. "gcc" / "gcap" to comment out blocks of text
+ :after (evil)
+ :commands (evil-commentary-mode) :demand t
+ :config (evil-commentary-mode +1)
+ :diminish evil-commentary-mode)
+
+(use-package evil-expat ; for :reverse, :remove, :rename, :colo, :g*, ... ex commands
+ :after (evil))
+
+(use-package evil-surround
+ :after (evil)
+ :commands (global-evil-surround-mode) :demand t
+ :config (global-evil-surround-mode +1))
+
+(use-package smartparens ; required by evil-cleverparens
+ ;; :custom
+ ;; (sp-sexp-prefix '() "Set up Guix gexp-related sexp prefixes.")
+ )
+
+(use-package evil-cleverparens
+ :after (evil smartparens)
+ :hook ((lisp-mode lisp-data-mode scheme-mode) . evil-cleverparens-mode)
+ :custom
+ (evil-cleverparens-use-additional-movement-keys nil "Disable overriding of standard vim bracket navigation keys."))
+
+(use-package evil-multiedit
+ ;; See: https://github.com/hlissner/evil-multiedit#usage
+ :commands (evil-multiedit-default-keybinds) :demand t
+ :config (evil-multiedit-default-keybinds))
+
+(use-package evil-args
+ :after (evil)
+ :config
+ ;; Bind evil-args text objects only.
+ ;; See https://github.com/wcsmith/evil-args for more bindings.
+ (define-key evil-inner-text-objects-map "a" 'evil-inner-arg)
+ (define-key evil-outer-text-objects-map "a" 'evil-outer-arg))
+
+(use-package evil-numbers
+ :after (evil)
+ :bind (("<leader>+" . evil-numbers/inc-at-pt)
+ ("<leader>-" . evil-numbers/dec-at-pt)))
+
+(use-package evil-goggles ; visual previews for edit operations
+ :after (evil)
+ :commands (evil-goggles-mode evil-goggles-use-diff-faces) :demand t
+ :config
+ (evil-goggles-mode +1)
+ (evil-goggles-use-diff-faces)
+ :diminish evil-goggles-mode)
+
+(use-package evil-traces ; visual previews for :ex commands
+ :after (evil)
+ :commands (evil-traces-mode evil-traces-use-diff-faces) :demand t
+ :config
+ (evil-traces-mode +1)
+ (evil-traces-use-diff-faces)
+ :diminish evil-traces-mode)
+
+(use-package evil-markdown
+ :after (evil markdown-mode)
+ :hook markdown-mode)
+
+;; Lots of useful text objects and keybinds:
+;; https://github.com/iyefrat/evil-tex#incomplete-showcase
+(use-package evil-tex
+ :after (evil tex-mode)
+ :hook TeX-mode)
+
+(use-package evil-text-object-python
+ :after (evil python)
+ :hook (python-mode . evil-text-object-python-add-bindings))
+
+;; Lisp features
+(use-package aggressive-indent
+ :hook (scheme-mode emacs-lisp-mode lisp-mode sh-mode bash-ts-mode))
+
+(defun tw/find-asd-systems (directory)
+ "Return a list of Common Lisp .asd systems found in DIRECTORY."
+ (let ((asd-rx (rx ".asd" eos)))
+ ;; `locate-dominating-file' will call this function once with the original
+ ;; file name as DIRECTORY, but `directory-files' fails if its argument is
+ ;; a regular file, so protect against this.
+ (and (directory-name-p directory)
+ (mapcar (lambda (file)
+ (string-trim-right file asd-rx))
+ (directory-files directory nil asd-rx)))))
+
+(defun tw/lisp-project-setup ()
+ "Set up a Lisp REPL for the current project."
+ (when-let ((fname (buffer-file-name))
+ (project-directory
+ (or (locate-dominating-file fname "guix.scm")
+ (locate-dominating-file fname #'tw/find-asd-systems)
+ (project-current nil (file-name-directory fname)))))
+ (cd project-directory)
+ (setq-local
+ inferior-lisp-program
+ `(;; If a guix.scm file exists, run Lisp in a Guix shell to get dependencies.
+ ,@(and (file-exists-p (file-name-concat project-directory "guix.scm"))
+ '("guix" "shell" "-Df" "guix.scm" "--"))
+ "sbcl" "--noinform"
+ ;; Load all defined asdf systems.
+ ,@(mapcan (lambda (system)
+ (list "--load" (format "%s.asd" system)
+ "--eval" (format "(require '%s)" system)))
+ ;; Heuristic: shorter names are earlier in the dependency tree.
+ ;; For example, X-test.asd depends on X.asd.
+ (sort (tw/find-asd-systems project-directory)
+ (lambda (s1 s2)
+ (< (length s1) (length s2)))))
+ ;; Assume the project directory name is the name of the main package.
+ "--eval" ,(format "(in-package #:%s)"
+ (file-name-base
+ (directory-file-name project-directory)))))))
+
+(use-package inf-lisp
+ :after (lisp-mode)
+ :hook (lisp-mode . tw/lisp-project-setup)
+ :custom
+ (inferior-lisp-program "sbcl"))
+
+(defun tw/lisp-evil-setup ()
+ "Set up evil in general `lisp-mode' buffers."
+ ;; https://github.com/wcsmith/evil-args#customization
+ (setq-local evil-args-delimiters '(" ")))
+
+;; Sadly, not all Lisp modes derive from `lisp-mode'.
+(add-hook 'lisp-mode-hook #'tw/lisp-evil-setup)
+(add-hook 'lisp-data-mode-hook #'tw/lisp-evil-setup) ; for elisp
+(add-hook 'scheme-mode-hook #'tw/lisp-evil-setup)
+
+;; buffer-locally set `evil-lookup-func' (used on K keys) for
+;; languages where something better than man pages is available
+;; (e.g. `describe-symbol' for elisp).
+(defun tw/elisp-lookup-func ()
+ "Show help in `emacs-lisp-mode' buffers."
+ (let ((sym (symbol-at-point)))
+ (if sym (describe-symbol sym)
+ (call-interactively #'describe-symbol))))
+
+(defun tw/emacs-lisp-evil-setup ()
+ "Set up evil in `emacs-lisp-mode' buffers."
+ (setq-local evil-lookup-func #'tw/elisp-lookup-func))
+
+(add-hook 'emacs-lisp-mode-hook #'tw/emacs-lisp-evil-setup)
+
+(evil-define-key '(normal visual) emacs-lisp-mode-map
+ (kbd "<localleader>eb") #'eval-buffer
+ (kbd "<localleader>ef") #'eval-defun
+ (kbd "<localleader>er") #'eval-region
+ (kbd "<localleader>el") #'eval-last-sexp
+ (kbd "<localleader>ep") #'eval-print-last-sexp)
+
+;; Guix-related .dir-locals.el entries. These are fine; don't prompt every time.
+(add-to-list 'safe-local-variable-values '(geiser-repl-per-project-p . t))
+(add-to-list 'safe-local-variable-values '(geiser-guile-binary . ("guix" "repl")))
+(mapc (apply-partially #'add-to-list 'safe-local-eval-forms)
+ '((modify-syntax-entry 126 "'")
+ (modify-syntax-entry 36 "'")
+ (modify-syntax-entry 43 "'")
+ (let ((root-dir-unexpanded (locate-dominating-file default-directory ".dir-locals.el")))
+ (when root-dir-unexpanded
+ (let* ((root-dir (file-local-name (expand-file-name root-dir-unexpanded)))
+ (root-dir* (directory-file-name root-dir)))
+ (unless (boundp 'geiser-guile-load-path)
+ (defvar geiser-guile-load-path 'nil))
+ (make-local-variable 'geiser-guile-load-path)
+ (require 'cl-lib)
+ (cl-pushnew root-dir* geiser-guile-load-path :test #'string-equal))))
+ (progn
+ (require 'lisp-mode)
+ (defun emacs27-lisp-fill-paragraph (&optional justify)
+ (interactive "P")
+ (or (fill-comment-paragraph justify)
+ (let ((paragraph-start
+ (concat paragraph-start "\\|\\s-*\\([(;\"]\\|\\s-:\\|`(\\|#'(\\)"))
+ (paragraph-separate
+ (concat paragraph-separate "\\|\\s-*\".*[,\\.]$"))
+ (fill-column
+ (if (and (integerp emacs-lisp-docstring-fill-column)
+ (derived-mode-p 'emacs-lisp-mode))
+ emacs-lisp-docstring-fill-column
+ fill-column)))
+ (fill-paragraph justify))
+ t))
+ (setq-local fill-paragraph-function #'emacs27-lisp-fill-paragraph))
+
+ ;; Forms used by Guix upstream.
+ (add-to-list 'completion-ignored-extensions ".go")
+ (setq-local guix-directory (locate-dominating-file
+ default-directory ".dir-locals.el"))
+ (with-eval-after-load 'yasnippet
+ (let ((guix-yasnippets (expand-file-name
+ "etc/snippets/yas"
+ (locate-dominating-file
+ default-directory ".dir-locals.el"))))
+ (unless (member guix-yasnippets yas-snippet-dirs)
+ (add-to-list 'yas-snippet-dirs guix-yasnippets)
+ (yas-reload-all))))
+ (let ((root-dir-unexpanded (locate-dominating-file
+ default-directory ".dir-locals.el")))
+ (when root-dir-unexpanded
+ (let* ((root-dir (expand-file-name root-dir-unexpanded))
+ (root-dir* (directory-file-name root-dir)))
+ (unless (boundp 'geiser-guile-load-path)
+ (defvar geiser-guile-load-path 'nil))
+ (make-local-variable 'geiser-guile-load-path)
+ (require 'cl-lib)
+ (cl-pushnew root-dir* geiser-guile-load-path
+ :test #'string-equal))))))
+
+;;; init.el ends here
diff --git a/tw/services/files/emacs-packages/actionlint.el b/tw/services/files/emacs-packages/actionlint.el
new file mode 100644
index 00000000..68a25c57
--- /dev/null
+++ b/tw/services/files/emacs-packages/actionlint.el
@@ -0,0 +1,147 @@
+;;; actionlint.el --- Flycheck checker for GitHub Actions. -*- lexical-binding: t -*-
+;;; Commentary:
+;; GitHub Actions are defined using mostly plain YAML files.
+;; Actionlint is a linter catching GitHub Action-specific mistakes, and also
+;; checks Shell and Python code embedded in Actions (using shellcheck and
+;; pyflakes, respectively).
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+
+(defgroup actionlint nil
+ "Actionlint-related options."
+ :group 'languages
+ :prefix "actionlint-")
+
+(defcustom actionlint-executable "actionlint"
+ "The alidistlint executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'actionlint)
+
+(defvar actionlint--message-regexp
+ (rx bol "<stdin>:" ; filename
+ (group-n 2 (+ digit)) ":" ; line
+ (group-n 3 (+ digit)) ": " ; column
+ (? (or (seq "pyflakes reported issue in this script: "
+ (group-n 4 (+ digit)) ":" ; inner line
+ (group-n 5 (+ digit)) " ") ; inner column
+ (seq "shellcheck reported issue in this script: "
+ (group-n 8 "SC" (+ digit)) ":" ; shellcheck code
+ (group-n 6 (or "info" "warning" "error")) ":" ; type
+ (group-n 4 (+ digit)) ":" ; inner line
+ (group-n 5 (+ digit)) ": "))) ; inner column
+ (group-n 1 (+? not-newline)) " " ; message
+ "[" (group-n 7 (+ (not ?\]))) "]" eol) ; backend/error name
+ "Regular expression matching messages reported by actionlint.
+
+The following convention for match groups is used:
+
+ 1. free-form message
+ 2. outer line number
+ 3. outer column number
+ 4. (optional) inner line number
+ 5. (optional) inner column number
+ 6. (optional) error level/type
+ 7. backend/error name (e.g. syntax-check or pyflakes)
+ 8. (optional) backend-specific error code
+
+The outer line/column numbers are always present and refer to the location of
+the key where the error is, normally. If the message was passed through from
+another linter (e.g. shellcheck), it may have an inner line/column, which will
+be relative to the contents of the key instead.")
+
+(defun actionlint--next-message (source)
+ "Return the next message according to REGEXP for buffer SOURCE, if any."
+ (when-let* ((match (search-forward-regexp actionlint--message-regexp nil t))
+ (inner-line (if-let ((match (match-string 4)))
+ ;; 1-based; don't subtract 1 since we assume
+ ;; that the script actually starts on the next
+ ;; line.
+ (string-to-number match)
+ 0))
+ (inner-column (if-let ((match (match-string 5)))
+ ;; 1-based; add 1 (assuming 2-space indents)
+ ;; to pick the right place inside the string.
+ (1+ (string-to-number match))
+ 0))
+ (region (flymake-diag-region
+ source
+ (+ (string-to-number (match-string 2)) inner-line)
+ (+ (string-to-number (match-string 3)) inner-column)))
+ (type (pcase (match-string 6)
+ ("info" :note)
+ ("warning" :warning)
+ ("error" :error)
+ ('nil :error)))
+ (message (if-let ((code (match-string 8)))
+ (concat (match-string 1) " (" (match-string 7) " " code ")")
+ (concat (match-string 1) " (" (match-string 7) ")"))))
+ (flymake-make-diagnostic source (car region) (cdr region) type message)))
+
+(defvar-local actionlint--flymake-proc nil
+ "The latest invocation of actionlint.")
+
+;; See info node: (flymake)An annotated example backend.
+(defun actionlint-flymake (report-fn &rest _args)
+ "Run actionlint and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find actionlint-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `actionlint-executable' program")
+ (error "Cannot find actionlint executable"))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p actionlint--flymake-proc)
+ (kill-process actionlint--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq actionlint--flymake-proc
+ (make-process
+ :name "actionlint-flymake" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *actionlint-flymake*")
+ :command (list actionlint-executable "-oneline" "-no-color" "-")
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (eq proc actionlint--flymake-proc))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-do (diags
+ (msg (actionlint--next-message source)
+ (actionlint--next-message source)))
+ ((null msg)
+ (funcall report-fn diags))
+ (push msg diags)))
+ (flymake-log :warning "Canceling obsolete check %s" proc))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc)))))))
+ ;; Send the buffer to actionlint on stdin.
+ (process-send-region actionlint--flymake-proc (point-min) (point-max))
+ (process-send-eof actionlint--flymake-proc))))
+
+(defun actionlint-github-workflow-p ()
+ "Does the current buffer contain a GitHub Action?"
+ (let ((name (buffer-file-name)))
+ (and name (string-match-p
+ (rx ".github/workflows/" (+ (not ?\/)) ".yml" eos) name))))
+
+(defun actionlint-setup ()
+ "Set up actionlint in this buffer, if it is recognised as a workflow file."
+ (when (actionlint-github-workflow-p)
+ (add-hook 'flymake-diagnostic-functions #'actionlint-flymake nil t)))
+
+(add-hook 'yaml-mode-hook #'actionlint-setup)
+(add-hook 'yaml-ts-mode-hook #'actionlint-setup)
+
+(provide 'actionlint)
+;;; actionlint.el ends here
diff --git a/tw/services/files/emacs-packages/alidist-mode.el b/tw/services/files/emacs-packages/alidist-mode.el
new file mode 100644
index 00000000..fbcef7e5
--- /dev/null
+++ b/tw/services/files/emacs-packages/alidist-mode.el
@@ -0,0 +1,170 @@
+;;; alidist-mode.el --- Major mode for alidist recipes -*- lexical-binding: t -*-
+;;; Commentary:
+;;; alidist recipes are shell scripts with a YAML header in front. We
+;;; want both these parts highlighted properly, and to lint the whole
+;;; thing with a custom script that glues together yamllint and
+;;; shellcheck with a few custom checks.
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+(require 'mmm-mode)
+(require 'mmm-cmds)
+(require 'mmm-vars)
+(require 'sh-script)
+(require 'yaml-mode)
+
+(defgroup alidist-mode nil
+ "Alidist-related options."
+ :group 'languages
+ :prefix "alidist-mode-")
+
+(defcustom alidist-mode-alidistlint-executable "alidistlint"
+ "The alidistlint executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'alidist-mode)
+
+(defvar alidist-mode--message-regexp
+ (rx bol "<stdin>:" ; filename
+ (group (+ digit)) ":" ; line
+ (group (+ digit)) ": " ; column
+ (group (or "note" "warning" "error")) ": " ; type
+ (group (+ not-newline)) eol) ; message
+ "Regular expression matching messages from alidistlint.
+`alidist-flymake' expects the following capturing groups in this
+regexp: (1) line number; (2) column number; (3) error type; (4)
+message.")
+
+(defvar-local alidist-mode--flymake-proc nil
+ "The latest invocation of alidistlint.")
+
+;; See info node: (flymake)An annotated example backend.
+(defun alidist-flymake (report-fn &rest _args)
+ "Run alidistlint and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find alidist-mode-alidistlint-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `alidist-mode-alidistlint-executable' program")
+ (error "Cannot find alidistlint executable"))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p alidist-mode--flymake-proc)
+ (kill-process alidist-mode--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer)))
+ (save-restriction
+ (widen)
+ (setq alidist-mode--flymake-proc
+ (make-process
+ :name "alidistlint-flymake" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *alidistlint-flymake*")
+ :command (list alidist-mode-alidistlint-executable "-f" "gcc" "-")
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (eq proc alidist-mode--flymake-proc))
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ while (search-forward-regexp alidist-mode--message-regexp nil t)
+ for (beg . end) = (flymake-diag-region
+ source
+ (string-to-number (match-string 1))
+ (string-to-number (match-string 2)))
+ for type = (pcase (match-string 3)
+ ("note" :note)
+ ("warning" :warning)
+ ("error" :error)
+ (type (error "Unknown alidistlint error type %s" type)))
+ collect (flymake-make-diagnostic source beg end type (match-string 4))
+ into diags
+ finally (funcall report-fn diags)))
+ (flymake-log :warning "Canceling obsolete check %s" proc))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc)))))))
+ ;; Send the buffer to alidistlint on stdin.
+ (process-send-region alidist-mode--flymake-proc (point-min) (point-max))
+ (process-send-eof alidist-mode--flymake-proc))))
+
+(defvar-local alidist-mode--mmm-refresh-timer nil
+ "An idle timer for the current buffer, to make `mmm-mode' reparse it.")
+(put 'alidist-mode--mmm-refresh-timer 'risky-local-variable t)
+
+(defun alidist-mode--cancel-refresh-timer ()
+ "Cancel and delete the timer that reparses the buffer.
+It is stored in `alidist-mode--mmm-refresh-timer'."
+ (when alidist-mode--mmm-refresh-timer
+ (cancel-timer alidist-mode--mmm-refresh-timer)
+ (setq alidist-mode--mmm-refresh-timer nil)))
+
+(define-derived-mode alidist-mode yaml-mode "alidist"
+ "An outer mode for alidist recipes, handling the metadata."
+ (mmm-mode)
+ ;; `mmm-mode' doesn't refresh its submodes when the buffer changes
+ ;; (e.g. when a *_recipe key is added to the YAML header), so
+ ;; refresh manually when idle.
+ (alidist-mode--cancel-refresh-timer)
+ (add-hook 'kill-buffer-hook #'alidist-mode--cancel-refresh-timer 0 t)
+ (setq alidist-mode--mmm-refresh-timer
+ (run-with-idle-timer
+ 2 t (lambda (original-buffer)
+ (when (eq original-buffer (current-buffer))
+ ;; Silence `mmm-parse-buffer''s annoying message.
+ (let ((inhibit-message t))
+ (mmm-parse-buffer))))
+ ;; Idle timers are global, so make sure we only run the timer
+ ;; in the right buffer. Save the buffer now to enable this,
+ ;; and compare every time the timer ticks over.
+ (current-buffer)))
+ ;; Set up `flymake-mode'.
+ (add-hook 'flymake-diagnostic-functions #'alidist-flymake nil t)
+ (flymake-mode))
+
+(define-derived-mode alidist-script-ts-mode bash-ts-mode "Script"
+ "A mode for scripts in alidist recipes, using tree-sitter.")
+
+(define-derived-mode alidist-script-mode sh-mode "Script"
+ "A mode for scripts in alidist recipes with some default settings."
+ (sh-set-shell "bash"))
+
+(mmm-add-group
+ 'alidist-recipe
+ `((alidist-main-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ :front ,(rx line-start "---\n")
+ :back ,(rx buffer-end))
+ (alidist-option-script
+ :submode alidist-script-mode
+ :face mmm-default-submode-face
+ ;; Any *_recipe key with a multiline string value is probably a script.
+ :front ,(rx line-start (* whitespace)
+ (or "recipe" ; for recipes under prefer_system_replacement_specs
+ (seq (1+ (any alnum ?\_))
+ (or "_recipe" "_check")))
+ ": |\n")
+ ;; End of YAML header, or another YAML key.
+ :back ,(rx line-start
+ (or "---\n"
+ (seq (* whitespace) (+ (any alnum ?\_)) ":"
+ (or line-end whitespace)))))))
+
+;; Make `mmm-mode' remember `sh-mode'/`bash-ts-mode' indentation variables.
+(cl-dolist (var sh-var-list)
+ (cl-pushnew `(,var region (sh-mode bash-ts-mode))
+ mmm-save-local-variables :test 'equal))
+
+(mmm-add-mode-ext-class 'alidist-mode nil 'alidist-recipe)
+(add-to-list 'auto-mode-alist
+ (cons (rx (or bot "/") "alidist/" (1+ (not ?\/)) ".sh" eot)
+ #'alidist-mode))
+
+(provide 'alidist-mode)
+;;; alidist-mode.el ends here
diff --git a/tw/services/files/emacs-packages/bemscript-mode.el b/tw/services/files/emacs-packages/bemscript-mode.el
new file mode 100644
index 00000000..f46c858b
--- /dev/null
+++ b/tw/services/files/emacs-packages/bemscript-mode.el
@@ -0,0 +1,92 @@
+;;; bemscript-mode.el --- Syntax highlighting for MERRILL BEMScript files.
+;;; Commentary:
+;;; Based on the MERRILL manual. Some commands may have been missed.
+;;; Code:
+
+(defconst bemscript-mode-keywords
+ '("set" "setsubdomain" "magnetite" "iron" "tm54" "resize" "cubic anisotropy"
+ "uniaxial anisotropy" "cubicrotation" "easy axis" "external field strength"
+ "external field direction" "readmesh" "loadmesh" "readmagnetization"
+ "uniform magnetization" "randomize magnetization" "randomize all moments"
+ "remesh" "conjugategradient" "steepestdescent" "minimize" "energylog"
+ "closelogfile" "writeloopdata" "writemagnetization" "writedemag" "writehyst"
+ "writeboxdata" "appenddemagzone" "magnetizationtopath" "pathtomagnetization"
+ "renewpath" "refinepathto" "writetecplotpath" "readtecplotpath"
+ "readtecplotzone" "keypause" "makeinitialpath" "pathminimize" "pathlogfile"
+ "systemcommand" "pathstructureenergies" "reportenergy" "stop" "end" "loop"
+ "endloop" "define" "addto" "undefine" "generatecubemesh" "zonename")
+ "List of keywords for BEMScript mode. Intended for case folding.")
+
+(defconst bemscript-mode-builtins
+ '("Aex" "CurvatureWeight" "ExchangeCalculator" "K1" "K2" "Ls" "Ms" "mu"
+ "MaxEnergyEvaluations" "MaxPathEvaluations" "MaxRestart" "MaxMeshNumber"
+ "NEBSpring" "PathN" "Zone" "ZoneIncrement")
+ "List of built-in variable names for BEMScript.")
+
+(defconst bemscript-mode-special
+ '("patran" "tecplot" "POINT" "BLOCK" "SD" "muT" "mT" "T" "C")
+ "Variables with special meanings and units in BEMScript.")
+
+;; Available font-lock-*-faces: doc type string builtin comment keyword warning
+;; constant (reference) preprocessor syntactic-function function-name
+;; negation-char variable-name comment-delimiter
+(defconst bemscript-mode-font-lock-defaults
+ `((;; See font-lock-keywords docs. Earlier lines seem to override later ones,
+ ;; except if both have OVERRIDE? t.
+ ;; Format: (REGEXP (GROUPNUM FACENAME OVERRIDE? LAXMATCH?)...)
+ ;; Comments
+ ("!\\(.*\\)" 1 font-lock-comment-face t t)
+ ("!" 0 font-lock-comment-delimiter-face t)
+ ("!!.*$" 0 font-lock-doc-face t)
+
+ ;; Equals signs need spaces around them.
+ ("\\s-=\\s-" 0 font-lock-type-face t) ; there is no "operator" etc face
+ ("=" . font-lock-warning-face)
+
+ ;; Numbers and variables
+ ("\\<[0-9]*\\.?[0-9]+\\(e[-+]?\\)?[0-9]*\\>" . font-lock-constant-face)
+ ("\\(\\<[#%][A-Z_a-z][0-9A-Z_a-z]*\\>\\|\\$[A-Z_a-z][0-9A-Za-z_]*\\$\\)"
+ . font-lock-variable-name-face)
+
+ ;; Preprocessor (&-substitution)
+ (,(concat "\\([^&]\\|^\\)\\(&&\\)*" ; && escapes &
+ "\\(&\\([_a-zA-Z][_a-zA-Z0-9]*\\|{[_a-zA-Z][_a-zA-Z0-9]*}\\)\\)")
+ 3 font-lock-preprocessor-face)
+ (,(concat "\\([^&]\\|^\\)\\(&&\\)*" ; && escapes &
+ "\\(&\\($" ; bare &
+ "\\|[^&{_a-zA-Z]\\|{[^_a-zA-Z]" ; invalid char following & or &{
+ ; invalid name or unclosed {
+ "\\|{[_a-zA-Z][_0-9a-zA-Z]*\\([^_0-9a-zA-Z}]\\|$\\)\\)\\)")
+ 3 font-lock-warning-face t)
+
+ ;; Variable definitions
+ ("\\<\\(loop\\|define\\)\\s-+\\([_a-zA-Z][_a-zA-Z0-9]*\\)\\>"
+ 2 font-lock-function-name-face)
+ ("\\<\\(addto\\|undefine\\)\\s-+\\([_a-zA-Z][_a-zA-Z0-9]*\\)\\>"
+ 2 font-lock-variable-name-face)
+
+ ;; Keywords
+ (,(regexp-opt bemscript-mode-special 'words) . font-lock-string-face)
+ (,(regexp-opt bemscript-mode-keywords 'words) . font-lock-keyword-face)
+ (,(regexp-opt bemscript-mode-builtins 'words) . font-lock-builtin-face))
+
+ ;; KEYWORDS-ONLY: if t, no syntactic fontification (strings and comments)
+ nil
+ ;; CASE-FOLD: if t, make keywords case-insensitive.
+ t)
+ "Font lock settings for BEMScript mode.")
+
+(define-derived-mode bemscript-mode prog-mode "BEMScript"
+ "BEMScript-mode is used for editing MERRILL scripts."
+ (setq comment-start "!"
+ comment-end ""
+ tab-width 2
+ font-lock-defaults bemscript-mode-font-lock-defaults)
+ ;; Only word syntax entries are highlighted; add needed chars.
+ (modify-syntax-entry ?# "w")
+ ;; Strings in BEMScript are not quoted.
+ (modify-syntax-entry ?\" "w"))
+
+(add-to-list 'auto-mode-alist '("\\.bem\\'" . bemscript-mode))
+(provide 'bemscript-mode)
+;;; bemscript-mode.el ends here
diff --git a/tw/services/files/emacs-packages/environmentd-mode.el b/tw/services/files/emacs-packages/environmentd-mode.el
new file mode 100644
index 00000000..4bb8812e
--- /dev/null
+++ b/tw/services/files/emacs-packages/environmentd-mode.el
@@ -0,0 +1,46 @@
+;;; environmentd-mode.el --- Major mode for environment.d(5) files.
+
+;;; Commentary:
+
+;; This major mode font-locks files including /etc/environment and
+;; ~/.config/environment.d/*.conf. Their format is specified by the
+;; environment.d(5) man page.
+
+;;; Code:
+
+(defconst environmentd-mode/font-lock-defaults
+ '((("^[[:blank:]]+[^[:blank:]]+" . font-lock-warning-face) ; stray leading whitespace
+ ("^#+[[:blank:]]*" . font-lock-comment-delimiter-face)
+ ("^#+[[:blank:]]*\\(.*\\)$" 1 font-lock-comment-face)
+ ("\\\\[$\\]" . font-lock-string-face) ; escaped $ \
+ ("^\\([A-Za-z_][A-Za-z0-9_]*\\)\\(=\\)"
+ (1 font-lock-variable-name-face)
+ (2 font-lock-keyword-face))
+ ("\\(\\${\\)\\([A-Za-z_][A-Za-z0-9_]*\\)\\(:[+-]\\)[^}]*\\(}\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face)
+ (3 font-lock-keyword-face)
+ (4 font-lock-keyword-face)) ; ${X:-default}-variable references
+ ("\\(\\${\\)\\([A-Za-z_][A-Za-z0-9_]*\\)\\(}\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face)
+ (3 font-lock-keyword-face)) ; ${X}-variable references
+ ("\\(\\$\\)\\([A-Za-z_][A-Za-z0-9_]*\\)"
+ (1 font-lock-keyword-face)
+ (2 font-lock-variable-name-face))) ; $X-variable references
+ t nil ((?\' . "w") (?\" . "w")))
+ "Font lock settings for Environment.d mode. See `font-lock-defaults' for documentation.")
+
+(define-derived-mode environmentd-mode prog-mode "Environment.d"
+ "Environment.d mode is used for environment.d(5) files."
+ (setq-local comment-start "#"
+ comment-start-skip "#"
+ comment-end ""
+ font-lock-defaults environmentd-mode/font-lock-defaults))
+
+(add-to-list 'auto-mode-alist
+ '("/environment\\.d/[^/]+\\.conf\\'\\|\\`/etc/environment\\'"
+ . environmentd-mode))
+
+(provide 'environmentd-mode)
+;;; environmentd-mode.el ends here
diff --git a/tw/services/files/emacs-packages/flymake-guile.el b/tw/services/files/emacs-packages/flymake-guile.el
new file mode 100644
index 00000000..edfbce82
--- /dev/null
+++ b/tw/services/files/emacs-packages/flymake-guile.el
@@ -0,0 +1,123 @@
+;;; flymake-guile.el --- Flymake checker using `guild compile' -*- lexical-binding: t -*-
+;;; Commentary:
+;;; "guild compile" compiles Guile code to bytecode and can output a few basic
+;;; warnings. Let's use this as a linter!
+;;; Code:
+
+(require 'custom)
+(require 'flymake)
+(require 'geiser-impl) ; for `geiser-active-implementations'
+
+(defcustom flymake-guile-guild-executable "guild"
+ "The guild executable to use. This will be looked up in $PATH."
+ :type '(string)
+ :risky t
+ :group 'flymake-guile)
+
+(defvar-local flymake-guile--flymake-proc nil
+ "The latest invocation of guild compile.")
+
+(defvar-local flymake-guile--temp-file nil
+ "The temporary file name to pass to guild.")
+
+(defun flymake-guile--encode-filename (buffer-name)
+ "Create a safe temporary file name from BUFFER-NAME."
+ (concat "/tmp/flymake-guile-"
+ (string-replace
+ "/" "!" ; we don't want to create subdirs under /tmp
+ (or buffer-name
+ (format "temp-%s.scm"
+ (random most-positive-fixnum))))))
+
+;; See info node: (flymake)An annotated example backend.
+(defun flymake-guile (report-fn &rest _args)
+ "Run guild compile and report diagnostics from it using REPORT-FN.
+Any running invocations are killed before running another one."
+ (unless (executable-find flymake-guile-guild-executable)
+ (funcall report-fn :panic
+ :explanation "Cannot find `flymake-guile-guild-executable' program")
+ (error "Cannot find guild executable"))
+
+ (unless flymake-guile--temp-file
+ (setq-local flymake-guile--temp-file (flymake-guile--encode-filename (buffer-file-name))))
+
+ ;; Kill previous check, if it's still running.
+ (when (process-live-p flymake-guile--flymake-proc)
+ (kill-process flymake-guile--flymake-proc))
+
+ ;; This needs `lexical-binding'.
+ (let ((source (current-buffer))
+ ;; Copy `flymake-guile--temp-file' to a local var so that we can refer to it in the `lambda' below.
+ (temp-file flymake-guile--temp-file))
+ (save-restriction
+ (widen)
+ ;; Send the buffer to guild on stdin.
+ (with-temp-file flymake-guile--temp-file
+ (insert-buffer-substring-no-properties source))
+ (setq flymake-guile--flymake-proc
+ (make-process
+ :name "flymake-guild" :noquery t :connection-type 'pipe
+ ;; Direct output to a temporary buffer.
+ :buffer (generate-new-buffer " *flymake-guile*")
+ ;; Guild can't read from stdin; it needs a file.
+ :command (list flymake-guile-guild-executable "compile"
+ ;; See "guild --warn=help" for details.
+ ;; "--warn=unsupported-warning" ; ignore unsupported warning types
+ ;; "--warn=unused-variable" ; too many false positives from macros
+ "--warn=unused-toplevel"
+ "--warn=shadowed-toplevel"
+ "--warn=unbound-variable"
+ "--warn=macro-use-before-definition"
+ "--warn=use-before-definition"
+ "--warn=non-idempotent-definition"
+ "--warn=arity-mismatch"
+ "--warn=duplicate-case-datum"
+ "--warn=bad-case-datum"
+ "--warn=format"
+ "-L" (expand-file-name
+ (project-root (project-current nil (file-name-directory
+ (buffer-file-name source)))))
+ flymake-guile--temp-file)
+ :sentinel
+ (lambda (proc _event)
+ "Parse diagnostic messages once the process PROC has exited."
+ ;; Check the process has actually exited, not just been suspended.
+ (when (memq (process-status proc) '(exit signal))
+ (unwind-protect
+ ;; Only proceed if we've got the "latest" process.
+ (if (with-current-buffer source (not (eq proc flymake-guile--flymake-proc)))
+ (flymake-log :warning "Canceling obsolete check %s" proc)
+ (with-current-buffer (process-buffer proc)
+ (goto-char (point-min))
+ (cl-loop
+ with msg-regexp = (rx bol (literal temp-file) ":" ; filename
+ (group (+ digit)) ":" ; line
+ (group (+ digit)) ": " ; column
+ (group (or "warning" "error")) ": " ; type
+ (group (+ not-newline)) eol) ; message
+ while (search-forward-regexp msg-regexp nil t)
+ for (beg . end) = (flymake-diag-region
+ source ; we filter for messages matching our buffer in the regexp
+ (string-to-number (match-string 1))
+ ;; guild outputs 0-based column numbers
+ (1+ (string-to-number (match-string 2))))
+ for type = (pcase (match-string 3)
+ ("warning" :warning)
+ ("error" :error)
+ (type (error "Unknown guild error type %s" type)))
+ collect (flymake-make-diagnostic source beg end type (match-string 4))
+ into diags
+ finally (funcall report-fn diags))))
+ ;; Clean up temporary buffer.
+ (kill-buffer (process-buffer proc))
+ (delete-file temp-file)))))))))
+
+(defun flymake-guile-enable ()
+ "Set up the Guile checker for flymake, if in a Guile buffer."
+ (when (memq 'guile geiser-active-implementations)
+ (add-hook 'flymake-diagnostic-functions #'flymake-guile nil t)))
+
+(add-hook 'scheme-mode-hook #'flymake-guile-enable)
+
+(provide 'flymake-guile)
+;;; flymake-guile.el ends here
diff --git a/tw/services/files/emacs-packages/ifm-mode.el b/tw/services/files/emacs-packages/ifm-mode.el
new file mode 100644
index 00000000..7416588b
--- /dev/null
+++ b/tw/services/files/emacs-packages/ifm-mode.el
@@ -0,0 +1,18 @@
+(define-generic-mode 'ifm-mode
+ '("#")
+ '("title" "map" "require" "room" "join" "to" "dir" "exit" "go" "oneway"
+ "tag" "from" "link" "nolink" "item" "in" "note" "score" "need" "after"
+ "before" "leave" "all" "except" "cmd" "length" "start" "finish" "nodrop"
+ "nopath" "style" "hidden" "keep" "with" "until" "ignore" "give" "lost"
+ "do" "get" "drop" "until" "safe" "ignore" "goto" "endstyle")
+ '(("\\<\\(\\(north\\|south\\)\\(east\\|west\\)?\\|[ns][ew]?\\|east\\|west\\|[ew]\\)\\>"
+ . 'font-lock-builtin-face)
+ ("\\<\\([du]\\|down\\|up\\|in\\|out\\|last\\|it\\|them\\)\\>"
+ . 'font-lock-builtin-face)
+ ("\\<[0-9]+" . 'font-lock-constant-face)
+ ("\\<[_a-zA-Z][_0-9A-Za-z]*\\>" . 'font-lock-variable-name-face))
+ '("\\.ifm\\'")
+ nil
+ "A mode for interactive fiction manager files")
+
+(provide 'ifm-mode)
diff --git a/tw/services/files/emacs-packages/pam-env-mode.el b/tw/services/files/emacs-packages/pam-env-mode.el
new file mode 100644
index 00000000..75b0bf94
--- /dev/null
+++ b/tw/services/files/emacs-packages/pam-env-mode.el
@@ -0,0 +1,45 @@
+;;; pam-env.el --- Major mode for pam_env.conf(5) files.
+
+;;; Commentary:
+
+;; This major mode font-locks files including ~/.pam_environment and
+;; /etc/security/pam_env.conf, but notably not /etc/environment. Their format is
+;; specified by the pam_env.conf(5) man page.
+
+;; TODO: Only apply font-lock-variable-name-face to variable declarations if
+;; the previous line didn't end with a backslash. The following case didn't
+;; work (some declarations that should've been font-locked weren't):
+;; '("\\(?:^$\\|[^\\\\]\\)[\r\n]\\([^[:blank:]]+\\)"
+;; 1 font-lock-variable-name-face keep)
+
+;; pam_env does not support escaped double quotes ("). Single-quoted strings are
+;; not used as string delimiters. We can only match against word chars in
+;; `pam-env-mode/font-lock-defaults', so make double quotes word chars.
+
+;;; Code:
+
+(defconst pam-env-mode/font-lock-defaults
+ '((("^#+" . font-lock-comment-delimiter-face)
+ ("^#+[[:blank:]]*\\(.*\\)$" 1 font-lock-comment-face)
+ ("\\\\[@$\\]" . font-lock-string-face) ; escaped $ @ \
+ ("@{[^}]+}" . font-lock-builtin-face) ; @{}-variable references
+ ("\\${[^}]+}" . font-lock-variable-name-face) ; ${}-variable references
+ ("\"[^\"]*\"" 0 font-lock-string-face keep) ; double-quoted strings; escaped " not supported
+ ("\\<\\(DEFAULT\\|OVERRIDE\\)=" . font-lock-keyword-face) ; DEFAULT= and OVERRIDE=
+ ("^[^[:blank:]]+" . font-lock-variable-name-face) ; variable declarations
+ ("[[:blank:]]+[^[:blank:]]+" . font-lock-warning-face)) ; stray whitespace
+ t nil ((?\' . "w") (?\" . "w")))
+ "Font lock settings for PAM-Environment mode. See `font-lock-defaults' for documentation.")
+
+(define-derived-mode pam-env-mode prog-mode "PAM-Environment"
+ "PAM-environment mode is used for pam_env.conf(5) files."
+ (set (make-local-variable 'comment-start) "#")
+ (set (make-local-variable 'comment-start-skip) "^#+[[:blank:]]*")
+ (set (make-local-variable 'comment-end) "")
+ (set (make-local-variable 'font-lock-defaults) pam-env-mode/font-lock-defaults))
+
+(let ((regexp "\\(\\`\\|/\\)\\(pam_env\\.conf\\|\\.pam_environment\\)\\'"))
+ (add-to-list 'auto-mode-alist `(,regexp . pam-env-mode)))
+
+(provide 'pam-env-mode)
+;;; pam-env.el ends here
diff --git a/tw/services/files/emacs-packages/vcard-mode.el b/tw/services/files/emacs-packages/vcard-mode.el
new file mode 100644
index 00000000..a932477a
--- /dev/null
+++ b/tw/services/files/emacs-packages/vcard-mode.el
@@ -0,0 +1,56 @@
+;;; vcard-mode.el --- Major mode for vCard files.
+
+;; Copyright (C) 2012 Desmond O. Chang
+
+;; Author: Desmond O. Chang <dochang@gmail.com>
+;; Version: 0.1.0
+;; Keywords: files
+
+;; This program 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.
+
+;; This program 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 a major mode to edit vCard files.
+
+;; To install it, put this file under your load path. Then add the
+;; following to your .emacs file:
+
+;; (require 'vcard-mode)
+
+;; Or if you don't want to load it until editing a vCard file:
+
+;; (autoload 'vcard-mode "vcard-mode" "Major mode for vCard files" t)
+;; (add-to-list 'auto-mode-alist '("\\.vc\\(f\\|ard\\)\\'" . vcard-mode))
+
+;;; Code:
+
+(require 'generic)
+
+(defun vcard-mode-init ()
+ (set (make-local-variable 'paragraph-start) "BEGIN:VCARD"))
+
+;;;###autoload
+(define-generic-mode vcard-mode
+ '()
+ nil
+ '(("^BEGIN:VCARD" . font-lock-function-name-face)
+ (";[^:\n]+:" . font-lock-type-face)
+ ("^\\([^;:\n]+\\):?" . font-lock-keyword-face))
+ '("\\.\\(vcf\\|vcard\\)\\'")
+ '(vcard-mode-init)
+ "Generic mode for vCard files.")
+
+(provide 'vcard-mode)
+
+;;; vcard-mode.el ends here
diff --git a/tw/services/files/emacsclient.desktop b/tw/services/files/emacsclient.desktop
new file mode 100644
index 00000000..5e68ffef
--- /dev/null
+++ b/tw/services/files/emacsclient.desktop
@@ -0,0 +1,22 @@
+[Desktop Entry]
+Name=Emacs (Client)
+GenericName=Text Editor
+Comment=Edit text
+MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
+Exec=emacsclient --alternate-editor= --create-frame %F
+Icon=emacs
+Type=Application
+Terminal=false
+Categories=Development;TextEditor;
+StartupNotify=true
+StartupWMClass=Emacs
+Keywords=emacsclient;
+Actions=new-window;new-instance;
+
+[Desktop Action new-window]
+Name=New Window
+Exec=emacsclient --alternate-editor= --create-frame %F
+
+[Desktop Action new-instance]
+Name=New Instance
+Exec=emacs %F
diff --git a/tw/services/files/gtk2.ini b/tw/services/files/gtk2.ini
new file mode 100644
index 00000000..6af8200a
--- /dev/null
+++ b/tw/services/files/gtk2.ini
@@ -0,0 +1,20 @@
+gtk-theme-name="Catppuccin-Dark"
+gtk-icon-theme-name="Papirus-Dark"
+gtk-font-name="Fira Sans, 10"
+gtk-cursor-theme-name="Catppuccin-Mocha-Dark-Cursors"
+gtk-cursor-theme-size=0
+gtk-toolbar-style=GTK_TOOLBAR_BOTH_HORIZ
+gtk-toolbar-icon-size=GTK_ICON_SIZE_MENU
+gtk-button-images=1
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle="hintslight"
+gtk-xft-rgba="rgb"
+#gtk-modules="appmenu-gtk-module"
+gtk-enable-animations=1
+gtk-primary-button-warps-slider=0
+# Use Qt UI conventions, I think (at least this setting is from KDE).
+gtk-alternative-button-order = 1
diff --git a/tw/services/files/gtk3.ini b/tw/services/files/gtk3.ini
new file mode 100644
index 00000000..fff5946b
--- /dev/null
+++ b/tw/services/files/gtk3.ini
@@ -0,0 +1,16 @@
+[Settings]
+gtk-theme-name=Catppuccin-Dark
+gtk-icon-theme-name=Papirus-Dark
+gtk-font-name=Fira Sans 10
+gtk-cursor-theme-name=Catppuccin-Mocha-Dark-Cursors
+gtk-cursor-theme-size=0
+gtk-toolbar-style=GTK_TOOLBAR_BOTH_HORIZ
+gtk-toolbar-icon-size=GTK_ICON_SIZE_MENU
+gtk-button-images=1
+gtk-menu-images=1
+gtk-enable-event-sounds=1
+gtk-enable-input-feedback-sounds=1
+gtk-xft-antialias=1
+gtk-xft-hinting=1
+gtk-xft-hintstyle=hintslight
+gtk-xft-rgba=rgb
diff --git a/tw/services/files/i3.conf b/tw/services/files/i3.conf
new file mode 100644
index 00000000..10ae1133
--- /dev/null
+++ b/tw/services/files/i3.conf
@@ -0,0 +1,273 @@
+#
+# i3 config file (v4)
+#
+# Please see http://i3wm.org/docs/userguide.html for a complete reference!
+# Use xev to see keycodes for special keys.
+
+# Appearance and Styling {{{
+# Font for window titles. Will also be used by the bar unless a different font
+# is used in the bar {} block below.
+font pango:Fira Sans 10
+
+title_align center
+default_border pixel 3
+default_floating_border pixel 3
+hide_edge_borders smart
+gaps inner 15
+gaps outer 0
+smart_gaps on
+
+# Catppuccin colours
+# Foregrounds
+set $c_fg #cdd6f4
+set $c_inactive #7f849c
+# Backgrounds
+set $c_bg #11111b
+set $c_active #585b70
+set $c_urgent #f9e2af
+
+# class border background text indicator child_border
+client.focused $c_active $c_active $c_fg $c_active $c_active
+client.focused_inactive $c_bg $c_bg $c_fg $c_bg $c_bg
+client.unfocused $c_bg $c_bg $c_inactive $c_bg $c_bg
+client.urgent $c_urgent $c_urgent $c_fg $c_urgent $c_urgent
+client.placeholder $c_bg $c_bg $c_inactive $c_bg $c_bg
+client.background $c_bg
+# }}}
+
+# Basic Configuration {{{
+# use the Super key as $mod
+set $mod Mod4
+
+# Use Mouse+$mod to drag floating windows to their wanted position
+floating_modifier $mod
+
+# essential applications
+bindsym $mod+Return exec kitty "--directory=$(xcwd)"
+bindsym $mod+i exec icecat --new-window
+bindsym $mod+shift+i exec icecat --new-tab "$(xclip -out)"
+bindsym $mod+e exec $ASYNC_EDITOR
+# Use ASYNC_EDITOR so I can continue using ranger while the editor
+# remains open in a separate window.
+bindsym $mod+slash exec EDITOR=$ASYNC_EDITOR kitty ranger "$(xcwd)"
+bindsym $mod+semicolon exec --no-startup-id nheko
+bindsym $mod+shift+colon exec --no-startup-id kitty aerc
+bindsym $mod+y exec kitty pulsemixer
+
+# volume
+bindsym XF86AudioRaiseVolume exec --no-startup-id volume +5
+bindsym XF86AudioLowerVolume exec --no-startup-id volume -5
+bindsym XF86AudioMute exec --no-startup-id volume toggle-mute
+
+# notifications
+bindsym $mod+n exec dunstctl close
+bindsym $mod+shift+n exec dunstctl history-pop
+bindsym $mod+period exec dunstctl context
+
+# screen locking
+bindsym $mod+shift+slash exec screenlock
+
+# kill focused window
+bindsym $mod+shift+q kill
+
+# rofi menus
+bindsym $mod+d exec rofi -show combi
+bindsym $mod+shift+e exec rofi -show session:sessionmenu
+# When selecting a history line in rofi-calc, it is printed; copy it so we can use it elsewhere.
+bindsym $mod+q exec rofi -show calc | xclip -quiet -i -rmlastnl -selection clipboard
+bindsym $mod+p exec passmenu --type-all
+bindsym $mod+shift+p exec passmenu --type-pass
+bindsym $mod+o exec passmenu --type-otp
+bindsym $mod+comma exec rofi-colors.sh
+bindsym $mod+shift+Return exec rofi -show ssh
+# }}}
+
+# Basic Movement {{{
+# change focus
+bindsym $mod+h focus left
+bindsym $mod+j focus down
+bindsym $mod+k focus up
+bindsym $mod+l focus right
+bindsym $mod+Left focus left
+bindsym $mod+Down focus down
+bindsym $mod+Up focus up
+bindsym $mod+Right focus right
+
+# move focused window
+bindsym $mod+shift+h move left
+bindsym $mod+shift+j move down
+bindsym $mod+shift+k move up
+bindsym $mod+shift+l move right
+bindsym $mod+shift+Left move left
+bindsym $mod+shift+Down move down
+bindsym $mod+shift+Up move up
+bindsym $mod+shift+Right move right
+
+bindsym $mod+a focus parent
+bindsym $mod+shift+a focus child
+# }}}
+
+# Layout {{{
+bindsym $mod+b split h
+bindsym $mod+v split v
+
+# enter fullscreen mode for the focused container
+bindsym $mod+f fullscreen toggle
+bindsym $mod+shift+f floating enable, resize set 1920 1080, move position 0 0
+
+# change container layout (stacked, tabbed, toggle split)
+bindsym $mod+s layout stacking
+bindsym $mod+w layout tabbed
+bindsym $mod+x layout toggle split
+
+bindsym $mod+shift+space floating toggle
+# change focus between tiling / floating windows
+bindsym $mod+space focus mode_toggle
+
+# scratchpad
+bindsym $mod+numbersign scratchpad show
+bindsym $mod+shift+numbersign move to scratchpad
+bindsym $mod+apostrophe sticky disable, floating disable
+bindsym $mod+shift+apostrophe sticky enable, floating enable
+
+# move workspaces
+bindsym $mod+shift+comma move workspace to output left
+bindsym $mod+shift+period move workspace to output right
+# }}}
+
+# Default Workspaces {{{
+# switch to workspace
+bindsym $mod+Escape workspace back_and_forth
+bindsym $mod+1 workspace 1
+bindsym $mod+2 workspace 2
+bindsym $mod+3 workspace 3
+bindsym $mod+4 workspace 4
+bindsym $mod+5 workspace 5
+bindsym $mod+6 workspace 6
+bindsym $mod+7 workspace 7
+bindsym $mod+8 workspace 8
+bindsym $mod+9 workspace 9
+bindsym $mod+0 workspace 10
+bindsym $mod+parenleft workspace prev
+bindsym $mod+parenright workspace next
+
+# move focused container to workspace
+bindsym $mod+shift+Escape move container to workspace back_and_forth
+bindsym $mod+shift+1 move container to workspace 1
+bindsym $mod+shift+2 move container to workspace 2
+bindsym $mod+shift+3 move container to workspace 3
+bindsym $mod+shift+4 move container to workspace 4
+bindsym $mod+shift+5 move container to workspace 5
+bindsym $mod+shift+6 move container to workspace 6
+bindsym $mod+shift+7 move container to workspace 7
+bindsym $mod+shift+8 move container to workspace 8
+bindsym $mod+shift+9 move container to workspace 9
+bindsym $mod+shift+0 move container to workspace 10
+bindsym $mod+shift+parenleft move container to workspace prev
+bindsym $mod+shift+parenright move container to workspace next
+# }}}
+
+# Custom Workspaces {{{
+set $ws_terminal 0:>_
+bindsym $mod+grave workspace "$ws_terminal"
+bindsym $mod+shift+grave move container to workspace "$ws_terminal"
+
+set $ws_coding 50:🖊️
+bindsym $mod+c workspace "$ws_coding"
+bindsym $mod+shift+c move container to workspace "$ws_coding"
+
+set $ws_games 60:🎮
+bindsym $mod+g workspace "$ws_games"
+bindsym $mod+shift+g move container to workspace "$ws_games"
+
+set $ws_music 80:🎵
+bindsym $mod+u workspace "$ws_music"
+bindsym $mod+shift+u move container to workspace "$ws_music"
+
+set $ws_messaging 90:💬
+bindsym $mod+m workspace "$ws_messaging"
+bindsym $mod+shift+m move container to workspace "$ws_messaging"
+
+# set default screens for workspaces
+# laptop: eDP-1-1 is the laptop screen, HDMI-0 is the external screen.
+# work desktop: DP-1-8 is on the left, DP-2 is the wide screen on the right.
+# The first workspaces listed for each output will be active at startup.
+workspace 1 output HDMI-0 DP-1-8
+workspace "$ws_messaging" output eDP-1-1 DP-2
+workspace 2 output HDMI-0 DP-1-8
+workspace 3 output HDMI-0 DP-1-8
+workspace 4 output HDMI-0 DP-1-8
+workspace 5 output HDMI-0 DP-1-8
+workspace 6 output eDP-1-1 DP-2
+workspace 7 output eDP-1-1 DP-2
+workspace 8 output eDP-1-1 DP-2
+workspace 9 output eDP-1-1 DP-2
+workspace 10 output eDP-1-1 DP-2
+workspace "$ws_terminal" output eDP-1-1 DP-1-8
+workspace "$ws_coding" output HDMI-0 DP-2
+workspace "$ws_games" output HDMI-0 DP-1-8
+workspace "$ws_music" output eDP-1-1 DP-1-8
+
+assign [class="^Franz$"] "$ws_messaging"
+assign [window_role="^weechat$"] "$ws_messaging"
+assign [window_role="^mutt$"] "$ws_messaging"
+assign [class="^evolution-initial$"] "$ws_messaging"
+assign [class="^kontact$"] "$ws_messaging"
+
+# NOTE: no_focus will also be ignored for the first window on a workspace as
+# there shouldn’t be a reason to not focus the window in this case. This allows
+# for better usability in combination with workspace_layout. (From i3 docs)
+#no_focus [class="^Franz$"]
+#no_focus [window_role="^weechat$"]
+#no_focus [window_role="^mutt$"]
+#no_focus [class="^evolution-initial$"]
+
+## PlayOnLinux
+for_window [title="PlayOnLinux"] floating enable
+
+## Plasma/KDE: https://ryanlue.com/posts/2019-06-13-kde-i3
+# Don’t treat Plasma pop-ups as full-sized windows
+for_window [class="plasmashell"] floating enable
+# Don’t spawn an empty window for the Plasma Desktop
+for_window [title="Desktop — Plasma"] move scratchpad
+# Don’t let notifications and non-interactive pop-up windows steal focus
+#no_focus [class="plasmashell" window_type="on_screen_display"]
+# https://github.com/heckelson/i3-and-kde-plasma
+# Move notifications to top-right corner.
+#for_window [class="plasmashell" window_type="notification"] move up 400, move right 750, no_focus
+# Notifications appear in the centre of the screen. According to
+# https://old.reddit.com/r/i3wm/comments/bw1yfs/kde_notifications_appearing_in_the_centre_of/,
+# setting notifications to appear in the top-left corner gets placement right.
+no_focus [class="plasmashell" window_type="notification"]
+
+for_window [class="^Pyneedle$"] floating enable
+for_window [instance="^emacs-initial$"] floating enable
+for_window [class="^Spotify$"] move container to workspace "$ws_music"
+for_window [class="^Pidgin$"] move container to workspace "$ws_messaging"
+for_window [class="^kitty$" title="^aerc$"] move container to workspace "$ws_messaging"
+# annoying pop-up from Bluetooth network manager
+for_window [class="^.blueman-applet-real$"] kill
+# }}}
+
+# Modes {{{
+mode "resize" {
+ bindsym h resize shrink width 10 px or 10 ppt
+ bindsym j resize grow height 10 px or 10 ppt
+ bindsym k resize shrink height 10 px or 10 ppt
+ bindsym l resize grow width 10 px or 10 ppt
+
+ bindsym shift+h resize shrink width 5 px or 5 ppt
+ bindsym shift+j resize grow height 5 px or 5 ppt
+ bindsym shift+k resize shrink height 5 px or 5 ppt
+ bindsym shift+l resize grow width 5 px or 5 ppt
+
+ bindsym left resize shrink width 10 px or 10 ppt
+ bindsym down resize grow height 10 px or 10 ppt
+ bindsym up resize shrink height 10 px or 10 ppt
+ bindsym right resize grow width 10 px or 10 ppt
+
+ bindsym Return mode "default"
+ bindsym Escape mode "default"
+}
+bindsym $mod+r mode "resize"
+# }}}
diff --git a/tw/services/files/khal.conf b/tw/services/files/khal.conf
new file mode 100644
index 00000000..3240609a
--- /dev/null
+++ b/tw/services/files/khal.conf
@@ -0,0 +1,44 @@
+# https://lostpackets.de/khal/configure.html
+
+[default]
+default_calendar = Personal
+highlight_event_days = True
+show_all_days = True
+timedelta = 7d
+
+[highlight_days]
+default_color = dark gray
+method = foreground
+
+[locale]
+timeformat = %H:%M
+dateformat = %d/%m
+longdateformat = %d/%m/%Y
+datetimeformat = %d/%m %H:%M
+longdatetimeformat = %d/%m/%Y %H:%M
+weeknumbers = off
+
+[view]
+monthdisplay = firstfullweek
+
+[calendars]
+[[birthdays]]
+type = birthdays
+path = ~/.local/share/vdirsyncer/main-contacts/contacts
+color = dark red
+# Prefer other events' colours when colouring days.
+priority = -1
+
+[[indico]]
+path = ~/.local/share/vdirsyncer/indico
+color = yellow
+readonly = True
+
+[[cern]]
+path = ~/.local/share/vdirsyncer/cern
+color = yellow
+readonly = True
+
+[[calendars]]
+type = discover
+path = ~/.local/share/vdirsyncer/calendars/imported-*ics
diff --git a/tw/services/files/khard.conf b/tw/services/files/khard.conf
new file mode 100644
index 00000000..a3cfe41a
--- /dev/null
+++ b/tw/services/files/khard.conf
@@ -0,0 +1,22 @@
+# https://khard.readthedocs.io/en/latest/man/khard.conf.html
+
+[addressbooks]
+[[main]]
+path = ~/.local/share/vdirsyncer/main-contacts/contacts
+[[sandbox]]
+path = ~/.local/share/vdirsyncer/sandbox-contacts/sandbox-contacts
+
+[general]
+default_action = list
+merge_editor = vimdiff
+
+[contact table]
+display = formatted_name
+sort = last_name
+group_by_addressbook = yes
+localize_dates = yes
+show_nicknames = yes
+show_kinds = yes
+show_uids = no
+preferred_phone_number_type = pref, cell, home
+preferred_email_address_type = pref, home, work
diff --git a/tw/services/files/kitty.conf b/tw/services/files/kitty.conf
new file mode 100644
index 00000000..014e70f6
--- /dev/null
+++ b/tw/services/files/kitty.conf
@@ -0,0 +1,1337 @@
+# vim:fileencoding=utf-8:ft=conf:foldmethod=marker
+
+#: Fonts {{{
+
+#: kitty has very powerful font management. You can configure
+#: individual font faces and even specify special fonts for particular
+#: characters.
+
+font_family Hermit
+# bold_font auto
+# italic_font auto
+# bold_italic_font auto
+
+#: You can specify different fonts for the bold/italic/bold-italic
+#: variants. To get a full list of supported fonts use the `kitty
+#: list-fonts` command. By default they are derived automatically, by
+#: the OSes font system. Setting them manually is useful for font
+#: families that have many weight variants like Book, Medium, Thick,
+#: etc. For example::
+
+#: font_family Operator Mono Book
+#: bold_font Operator Mono Medium
+#: italic_font Operator Mono Book Italic
+#: bold_italic_font Operator Mono Medium Italic
+
+font_size 10.0
+
+#: Font size (in pts)
+
+# force_ltr no
+
+#: kitty does not support BIDI (bidirectional text), however, for RTL
+#: scripts, words are automatically displayed in RTL. That is to say,
+#: in an RTL script, the words "HELLO WORLD" display in kitty as
+#: "WORLD HELLO", and if you try to select a substring of an RTL-
+#: shaped string, you will get the character that would be there had
+#: the the string been LTR. For example, assuming the Hebrew word
+#: ירושלים, selecting the character that on the screen appears to be ם
+#: actually writes into the selection buffer the character י.
+
+#: kitty's default behavior is useful in conjunction with a filter to
+#: reverse the word order, however, if you wish to manipulate RTL
+#: glyphs, it can be very challenging to work with, so this option is
+#: provided to turn it off. Furthermore, this option can be used with
+#: the command line program GNU FriBidi
+#: <https://github.com/fribidi/fribidi#executable> to get BIDI
+#: support, because it will force kitty to always treat the text as
+#: LTR, which FriBidi expects for terminals.
+
+# adjust_line_height 0
+# adjust_column_width 0
+
+#: Change the size of each character cell kitty renders. You can use
+#: either numbers, which are interpreted as pixels or percentages
+#: (number followed by %), which are interpreted as percentages of the
+#: unmodified values. You can use negative pixels or percentages less
+#: than 100% to reduce sizes (but this might cause rendering
+#: artifacts).
+
+# symbol_map U+E0A0-U+E0A3,U+E0C0-U+E0C7 PowerlineSymbols
+
+#: Map the specified unicode codepoints to a particular font. Useful
+#: if you need special rendering for some symbols, such as for
+#: Powerline. Avoids the need for patched fonts. Each unicode code
+#: point is specified in the form U+<code point in hexadecimal>. You
+#: can specify multiple code points, separated by commas and ranges
+#: separated by hyphens. symbol_map itself can be specified multiple
+#: times. Syntax is::
+
+#: symbol_map codepoints Font Family Name
+
+# disable_ligatures never
+
+#: Choose how you want to handle multi-character ligatures. The
+#: default is to always render them. You can tell kitty to not render
+#: them when the cursor is over them by using cursor to make editing
+#: easier, or have kitty never render them at all by using always, if
+#: you don't like them. The ligature strategy can be set per-window
+#: either using the kitty remote control facility or by defining
+#: shortcuts for it in kitty.conf, for example::
+
+#: map alt+1 disable_ligatures_in active always
+#: map alt+2 disable_ligatures_in all never
+#: map alt+3 disable_ligatures_in tab cursor
+
+#: Note that this refers to programming ligatures, typically
+#: implemented using the calt OpenType feature. For disabling general
+#: ligatures, use the font_features setting.
+
+# font_features none
+
+#: Choose exactly which OpenType features to enable or disable. This
+#: is useful as some fonts might have features worthwhile in a
+#: terminal. For example, Fira Code Retina includes a discretionary
+#: feature, zero, which in that font changes the appearance of the
+#: zero (0), to make it more easily distinguishable from Ø. Fira Code
+#: Retina also includes other discretionary features known as
+#: Stylistic Sets which have the tags ss01 through ss20.
+
+#: Note that this code is indexed by PostScript name, and not the font
+#: family. This allows you to define very precise feature settings;
+#: e.g. you can disable a feature in the italic font but not in the
+#: regular font.
+
+#: On Linux, these are read from the FontConfig database first and
+#: then this, setting is applied, so they can be configured in a
+#: single, central place.
+
+#: To get the PostScript name for a font, use kitty + list-fonts
+#: --psnames:
+
+#: .. code-block:: sh
+
+#: $ kitty + list-fonts --psnames | grep Fira
+#: Fira Code
+#: Fira Code Bold (FiraCode-Bold)
+#: Fira Code Light (FiraCode-Light)
+#: Fira Code Medium (FiraCode-Medium)
+#: Fira Code Regular (FiraCode-Regular)
+#: Fira Code Retina (FiraCode-Retina)
+
+#: The part in brackets is the PostScript name.
+
+#: Enable alternate zero and oldstyle numerals::
+
+#: font_features FiraCode-Retina +zero +onum
+
+#: Enable only alternate zero::
+
+#: font_features FiraCode-Retina +zero
+
+#: Disable the normal ligatures, but keep the calt feature which (in
+#: this font) breaks up monotony::
+
+#: font_features TT2020StyleB-Regular -liga +calt
+
+#: In conjunction with force_ltr, you may want to disable Arabic
+#: shaping entirely, and only look at their isolated forms if they
+#: show up in a document. You can do this with e.g.::
+
+#: font_features UnifontMedium +isol -medi -fina -init
+
+# box_drawing_scale 0.001, 1, 1.5, 2
+
+#: Change the sizes of the lines used for the box drawing unicode
+#: characters These values are in pts. They will be scaled by the
+#: monitor DPI to arrive at a pixel value. There must be four values
+#: corresponding to thin, normal, thick, and very thick lines.
+
+#: }}}
+
+#: Cursor customization {{{
+
+# cursor #cccccc
+
+#: Default cursor color
+
+# cursor_text_color #111111
+
+#: Choose the color of text under the cursor. If you want it rendered
+#: with the background color of the cell underneath instead, use the
+#: special keyword: background
+
+# cursor_shape block
+
+#: The cursor shape can be one of (block, beam, underline)
+
+# cursor_beam_thickness 1.5
+
+#: Defines the thickness of the beam cursor (in pts)
+
+# cursor_underline_thickness 2.0
+
+#: Defines the thickness of the underline cursor (in pts)
+
+# cursor_blink_interval -1
+
+#: The interval (in seconds) at which to blink the cursor. Set to zero
+#: to disable blinking. Negative values mean use system default. Note
+#: that numbers smaller than repaint_delay will be limited to
+#: repaint_delay.
+
+# cursor_stop_blinking_after 15.0
+
+#: Stop blinking cursor after the specified number of seconds of
+#: keyboard inactivity. Set to zero to never stop blinking.
+
+#: }}}
+
+#: Scrollback {{{
+
+scrollback_lines 100000
+
+#: Number of lines of history to keep in memory for scrolling back.
+#: Memory is allocated on demand. Negative numbers are (effectively)
+#: infinite scrollback. Note that using very large scrollback is not
+#: recommended as it can slow down performance of the terminal and
+#: also use large amounts of RAM. Instead, consider using
+#: scrollback_pager_history_size.
+
+# scrollback_pager less --chop-long-lines --RAW-CONTROL-CHARS +INPUT_LINE_NUMBER
+
+#: Program with which to view scrollback in a new window. The
+#: scrollback buffer is passed as STDIN to this program. If you change
+#: it, make sure the program you use can handle ANSI escape sequences
+#: for colors and text formatting. INPUT_LINE_NUMBER in the command
+#: line above will be replaced by an integer representing which line
+#: should be at the top of the screen. Similarly CURSOR_LINE and
+#: CURSOR_COLUMN will be replaced by the current cursor position.
+
+scrollback_pager_history_size 1024
+
+#: Separate scrollback history size, used only for browsing the
+#: scrollback buffer (in MB). This separate buffer is not available
+#: for interactive scrolling but will be piped to the pager program
+#: when viewing scrollback buffer in a separate window. The current
+#: implementation stores the data in UTF-8, so approximatively 10000
+#: lines per megabyte at 100 chars per line, for pure ASCII text,
+#: unformatted text. A value of zero or less disables this feature.
+#: The maximum allowed size is 4GB.
+
+scrollback_fill_enlarged_window yes
+
+#: Fill new space with lines from the scrollback buffer after
+#: enlarging a window.
+
+# wheel_scroll_multiplier 5.0
+
+#: Modify the amount scrolled by the mouse wheel. Note this is only
+#: used for low precision scrolling devices, not for high precision
+#: scrolling on platforms such as macOS and Wayland. Use negative
+#: numbers to change scroll direction.
+
+# touch_scroll_multiplier 1.0
+
+#: Modify the amount scrolled by a touchpad. Note this is only used
+#: for high precision scrolling devices on platforms such as macOS and
+#: Wayland. Use negative numbers to change scroll direction.
+
+#: }}}
+
+#: Mouse {{{
+
+# mouse_hide_wait 3.0
+
+#: Hide mouse cursor after the specified number of seconds of the
+#: mouse not being used. Set to zero to disable mouse cursor hiding.
+#: Set to a negative value to hide the mouse cursor immediately when
+#: typing text. Disabled by default on macOS as getting it to work
+#: robustly with the ever-changing sea of bugs that is Cocoa is too
+#: much effort.
+
+# url_color #0087bd
+# url_style curly
+
+#: The color and style for highlighting URLs on mouse-over. url_style
+#: can be one of: none, single, double, curly
+
+open_url_modifiers shift
+# open_url_modifiers kitty_mod
+
+#: The modifier keys to press when clicking with the mouse on URLs to
+#: open the URL
+
+# open_url_with default
+
+#: The program with which to open URLs that are clicked on. The
+#: special value default means to use the operating system's default
+#: URL handler.
+
+# url_prefixes http https file ftp gemini irc gopher mailto news git
+
+#: The set of URL prefixes to look for when detecting a URL under the
+#: mouse cursor.
+
+# detect_urls yes
+
+#: Detect URLs under the mouse. Detected URLs are highlighted with an
+#: underline and the mouse cursor becomes a hand over them. Even if
+#: this option is disabled, URLs are still clickable.
+
+# copy_on_select no
+
+#: Copy to clipboard or a private buffer on select. With this set to
+#: clipboard, simply selecting text with the mouse will cause the text
+#: to be copied to clipboard. Useful on platforms such as macOS that
+#: do not have the concept of primary selections. You can instead
+#: specify a name such as a1 to copy to a private kitty buffer
+#: instead. Map a shortcut with the paste_from_buffer action to paste
+#: from this private buffer. For example::
+
+#: map cmd+shift+v paste_from_buffer a1
+
+#: Note that copying to the clipboard is a security risk, as all
+#: programs, including websites open in your browser can read the
+#: contents of the system clipboard.
+
+# strip_trailing_spaces never
+
+#: Remove spaces at the end of lines when copying to clipboard. A
+#: value of smart will do it when using normal selections, but not
+#: rectangle selections. always will always do it.
+
+# rectangle_select_modifiers ctrl+alt
+
+#: The modifiers to use rectangular selection (i.e. to select text in
+#: a rectangular block with the mouse)
+
+# terminal_select_modifiers shift
+
+#: The modifiers to override mouse selection even when a terminal
+#: application has grabbed the mouse
+
+# select_by_word_characters @-./_~?&=%+#
+
+#: Characters considered part of a word when double clicking. In
+#: addition to these characters any character that is marked as an
+#: alphanumeric character in the unicode database will be matched.
+
+# click_interval -1.0
+
+#: The interval between successive clicks to detect double/triple
+#: clicks (in seconds). Negative numbers will use the system default
+#: instead, if available, or fallback to 0.5.
+
+focus_follows_mouse yes
+
+#: Set the active window to the window under the mouse when moving the
+#: mouse around
+
+# pointer_shape_when_grabbed arrow
+
+#: The shape of the mouse pointer when the program running in the
+#: terminal grabs the mouse. Valid values are: arrow, beam and hand
+
+# default_pointer_shape beam
+
+#: The default shape of the mouse pointer. Valid values are: arrow,
+#: beam and hand
+
+# pointer_shape_when_dragging beam
+
+#: The default shape of the mouse pointer when dragging across text.
+#: Valid values are: arrow, beam and hand
+
+#: }}}
+
+#: Performance tuning {{{
+
+# repaint_delay 10
+
+#: Delay (in milliseconds) between screen updates. Decreasing it,
+#: increases frames-per-second (FPS) at the cost of more CPU usage.
+#: The default value yields ~100 FPS which is more than sufficient for
+#: most uses. Note that to actually achieve 100 FPS you have to either
+#: set sync_to_monitor to no or use a monitor with a high refresh
+#: rate. Also, to minimize latency when there is pending input to be
+#: processed, repaint_delay is ignored.
+
+# input_delay 3
+
+#: Delay (in milliseconds) before input from the program running in
+#: the terminal is processed. Note that decreasing it will increase
+#: responsiveness, but also increase CPU usage and might cause flicker
+#: in full screen programs that redraw the entire screen on each loop,
+#: because kitty is so fast that partial screen updates will be drawn.
+
+# sync_to_monitor yes
+
+#: Sync screen updates to the refresh rate of the monitor. This
+#: prevents tearing (https://en.wikipedia.org/wiki/Screen_tearing)
+#: when scrolling. However, it limits the rendering speed to the
+#: refresh rate of your monitor. With a very high speed mouse/high
+#: keyboard repeat rate, you may notice some slight input latency. If
+#: so, set this to no.
+
+#: }}}
+
+#: Terminal bell {{{
+
+# enable_audio_bell yes
+
+#: Enable/disable the audio bell. Useful in environments that require
+#: silence.
+
+# visual_bell_duration 0.0
+
+#: Visual bell duration. Flash the screen when a bell occurs for the
+#: specified number of seconds. Set to zero to disable.
+
+# window_alert_on_bell yes
+
+#: Request window attention on bell. Makes the dock icon bounce on
+#: macOS or the taskbar flash on linux.
+
+# bell_on_tab yes
+
+#: Show a bell symbol on the tab if a bell occurs in one of the
+#: windows in the tab and the window is not the currently focused
+#: window
+
+# command_on_bell none
+
+#: Program to run when a bell occurs.
+
+#: }}}
+
+#: Window layout {{{
+
+# remember_window_size yes
+# initial_window_width 640
+# initial_window_height 400
+
+#: If enabled, the window size will be remembered so that new
+#: instances of kitty will have the same size as the previous
+#: instance. If disabled, the window will initially have size
+#: configured by initial_window_width/height, in pixels. You can use a
+#: suffix of "c" on the width/height values to have them interpreted
+#: as number of cells instead of pixels.
+
+# enabled_layouts *
+
+#: The enabled window layouts. A comma separated list of layout names.
+#: The special value all means all layouts. The first listed layout
+#: will be used as the startup layout. Default configuration is all
+#: layouts in alphabetical order. For a list of available layouts, see
+#: the https://sw.kovidgoyal.net/kitty/index.html#layouts.
+
+# window_resize_step_cells 2
+# window_resize_step_lines 2
+
+#: The step size (in units of cell width/cell height) to use when
+#: resizing windows. The cells value is used for horizontal resizing
+#: and the lines value for vertical resizing.
+
+# window_border_width 0.5pt
+
+#: The width of window borders. Can be either in pixels (px) or pts
+#: (pt). Values in pts will be rounded to the nearest number of pixels
+#: based on screen resolution. If not specified the unit is assumed to
+#: be pts. Note that borders are displayed only when more than one
+#: window is visible. They are meant to separate multiple windows.
+
+# draw_minimal_borders yes
+
+#: Draw only the minimum borders needed. This means that only the
+#: minimum needed borders for inactive windows are drawn. That is only
+#: the borders that separate the inactive window from a neighbor. Note
+#: that setting a non-zero window margin overrides this and causes all
+#: borders to be drawn.
+
+# window_margin_width 0
+
+#: The window margin (in pts) (blank area outside the border). A
+#: single value sets all four sides. Two values set the vertical and
+#: horizontal sides. Three values set top, horizontal and bottom. Four
+#: values set top, right, bottom and left.
+
+# single_window_margin_width -1
+
+#: The window margin (in pts) to use when only a single window is
+#: visible. Negative values will cause the value of
+#: window_margin_width to be used instead. A single value sets all
+#: four sides. Two values set the vertical and horizontal sides. Three
+#: values set top, horizontal and bottom. Four values set top, right,
+#: bottom and left.
+
+# window_padding_width 0
+
+#: The window padding (in pts) (blank area between the text and the
+#: window border). A single value sets all four sides. Two values set
+#: the vertical and horizontal sides. Three values set top, horizontal
+#: and bottom. Four values set top, right, bottom and left.
+
+# placement_strategy center
+
+#: When the window size is not an exact multiple of the cell size, the
+#: cell area of the terminal window will have some extra padding on
+#: the sides. You can control how that padding is distributed with
+#: this option. Using a value of center means the cell area will be
+#: placed centrally. A value of top-left means the padding will be on
+#: only the bottom and right edges.
+
+# active_border_color #00ff00
+
+#: The color for the border of the active window. Set this to none to
+#: not draw borders around the active window.
+
+# inactive_border_color #cccccc
+
+#: The color for the border of inactive windows
+
+# bell_border_color #ff5a00
+
+#: The color for the border of inactive windows in which a bell has
+#: occurred
+
+# inactive_text_alpha 1.0
+
+#: Fade the text in inactive windows by the specified amount (a number
+#: between zero and one, with zero being fully faded).
+
+# hide_window_decorations no
+
+#: Hide the window decorations (title-bar and window borders) with
+#: yes. On macOS, titlebar-only can be used to only hide the titlebar.
+#: Whether this works and exactly what effect it has depends on the
+#: window manager/operating system.
+
+# resize_debounce_time 0.1
+
+#: The time (in seconds) to wait before redrawing the screen when a
+#: resize event is received. On platforms such as macOS, where the
+#: operating system sends events corresponding to the start and end of
+#: a resize, this number is ignored.
+
+# resize_draw_strategy static
+
+#: Choose how kitty draws a window while a resize is in progress. A
+#: value of static means draw the current window contents, mostly
+#: unchanged. A value of scale means draw the current window contents
+#: scaled. A value of blank means draw a blank window. A value of size
+#: means show the window size in cells.
+
+# resize_in_steps no
+
+#: Resize the OS window in steps as large as the cells, instead of
+#: with the usual pixel accuracy. Combined with an
+#: initial_window_width and initial_window_height in number of cells,
+#: this option can be used to keep the margins as small as possible
+#: when resizing the OS window. Note that this does not currently work
+#: on Wayland.
+
+# confirm_os_window_close 0
+
+#: Ask for confirmation when closing an OS window or a tab that has at
+#: least this number of kitty windows in it. A value of zero disables
+#: confirmation. This confirmation also applies to requests to quit
+#: the entire application (all OS windows, via the quit action).
+
+#: }}}
+
+#: Tab bar {{{
+
+# tab_bar_edge bottom
+
+#: Which edge to show the tab bar on, top or bottom
+
+# tab_bar_margin_width 0.0
+
+#: The margin to the left and right of the tab bar (in pts)
+
+# tab_bar_style fade
+
+#: The tab bar style, can be one of: fade, separator, powerline, or
+#: hidden. In the fade style, each tab's edges fade into the
+#: background color, in the separator style, tabs are separated by a
+#: configurable separator, and the powerline shows the tabs as a
+#: continuous line. If you use the hidden style, you might want to
+#: create a mapping for the select_tab action which presents you with
+#: a list of tabs and allows for easy switching to a tab.
+
+# tab_bar_min_tabs 2
+
+#: The minimum number of tabs that must exist before the tab bar is
+#: shown
+
+# tab_switch_strategy previous
+
+#: The algorithm to use when switching to a tab when the current tab
+#: is closed. The default of previous will switch to the last used
+#: tab. A value of left will switch to the tab to the left of the
+#: closed tab. A value of right will switch to the tab to the right of
+#: the closed tab. A value of last will switch to the right-most tab.
+
+# tab_fade 0.25 0.5 0.75 1
+
+#: Control how each tab fades into the background when using fade for
+#: the tab_bar_style. Each number is an alpha (between zero and one)
+#: that controls how much the corresponding cell fades into the
+#: background, with zero being no fade and one being full fade. You
+#: can change the number of cells used by adding/removing entries to
+#: this list.
+
+# tab_separator " ┇"
+
+#: The separator between tabs in the tab bar when using separator as
+#: the tab_bar_style.
+
+# tab_powerline_style angled
+
+#: The powerline separator style between tabs in the tab bar when
+#: using powerline as the tab_bar_style, can be one of: angled,
+#: slanted, or round.
+
+# tab_activity_symbol none
+
+#: Some text or a unicode symbol to show on the tab if a window in the
+#: tab that does not have focus has some activity.
+
+# tab_title_template "{title}"
+
+#: A template to render the tab title. The default just renders the
+#: title. If you wish to include the tab-index as well, use something
+#: like: {index}: {title}. Useful if you have shortcuts mapped for
+#: goto_tab N. In addition you can use {layout_name} for the current
+#: layout name and {num_windows} for the number of windows in the tab.
+#: Note that formatting is done by Python's string formatting
+#: machinery, so you can use, for instance, {layout_name[:2].upper()}
+#: to show only the first two letters of the layout name, upper-cased.
+#: If you want to style the text, you can use styling directives, for
+#: example: {fmt.fg.red}red{fmt.fg.default}normal{fmt.bg._00FF00}green
+#: bg{fmt.bg.normal}. Similarly, for bold and italic:
+#: {fmt.bold}bold{fmt.nobold}normal{fmt.italic}italic{fmt.noitalic}.
+
+# active_tab_title_template none
+
+#: Template to use for active tabs, if not specified falls back to
+#: tab_title_template.
+
+# active_tab_foreground #000
+# active_tab_background #eee
+# active_tab_font_style bold-italic
+# inactive_tab_foreground #444
+# inactive_tab_background #999
+# inactive_tab_font_style normal
+
+#: Tab bar colors and styles
+
+# tab_bar_background none
+
+#: Background color for the tab bar. Defaults to using the terminal
+#: background color.
+
+#: }}}
+
+#: Color scheme {{{
+
+# foreground #dddddd
+# background #000000
+
+#: The foreground and background colors
+
+# background_opacity 1.0
+
+#: The opacity of the background. A number between 0 and 1, where 1 is
+#: opaque and 0 is fully transparent. This will only work if
+#: supported by the OS (for instance, when using a compositor under
+#: X11). Note that it only sets the background color's opacity in
+#: cells that have the same background color as the default terminal
+#: background. This is so that things like the status bar in vim,
+#: powerline prompts, etc. still look good. But it means that if you
+#: use a color theme with a background color in your editor, it will
+#: not be rendered as transparent. Instead you should change the
+#: default background color in your kitty config and not use a
+#: background color in the editor color scheme. Or use the escape
+#: codes to set the terminals default colors in a shell script to
+#: launch your editor. Be aware that using a value less than 1.0 is a
+#: (possibly significant) performance hit. If you want to dynamically
+#: change transparency of windows set dynamic_background_opacity to
+#: yes (this is off by default as it has a performance cost)
+
+# background_image none
+
+#: Path to a background image. Must be in PNG format.
+
+# background_image_layout tiled
+
+#: Whether to tile or scale the background image.
+
+# background_image_linear no
+
+#: When background image is scaled, whether linear interpolation
+#: should be used.
+
+# dynamic_background_opacity no
+
+#: Allow changing of the background_opacity dynamically, using either
+#: keyboard shortcuts (increase_background_opacity and
+#: decrease_background_opacity) or the remote control facility.
+
+# background_tint 0.0
+
+#: How much to tint the background image by the background color. The
+#: tint is applied only under the text area, not margin/borders. Makes
+#: it easier to read the text. Tinting is done using the current
+#: background color for each window. This setting applies only if
+#: background_opacity is set and transparent windows are supported or
+#: background_image is set.
+
+# dim_opacity 0.75
+
+#: How much to dim text that has the DIM/FAINT attribute set. One
+#: means no dimming and zero means fully dimmed (i.e. invisible).
+
+# selection_foreground #000000
+
+#: The foreground for text selected with the mouse. A value of none
+#: means to leave the color unchanged.
+
+# selection_background #fffacd
+
+#: The background for text selected with the mouse.
+
+
+#: The 256 terminal colors. There are 8 basic colors, each color has a
+#: dull and bright version, for the first 16 colors. You can set the
+#: remaining 240 colors as color16 to color255.
+
+# color0 #000000
+# color8 #767676
+
+#: black
+
+# color1 #cc0403
+# color9 #f2201f
+
+#: red
+
+# color2 #19cb00
+# color10 #23fd00
+
+#: green
+
+# color3 #cecb00
+# color11 #fffd00
+
+#: yellow
+
+# color4 #0d73cc
+# color12 #1a8fff
+
+#: blue
+
+# color5 #cb1ed1
+# color13 #fd28ff
+
+#: magenta
+
+# color6 #0dcdcd
+# color14 #14ffff
+
+#: cyan
+
+# color7 #dddddd
+# color15 #ffffff
+
+#: white
+
+# mark1_foreground black
+
+#: Color for marks of type 1
+
+# mark1_background #98d3cb
+
+#: Color for marks of type 1 (light steel blue)
+
+# mark2_foreground black
+
+#: Color for marks of type 2
+
+# mark2_background #f2dcd3
+
+#: Color for marks of type 1 (beige)
+
+# mark3_foreground black
+
+#: Color for marks of type 3
+
+# mark3_background #f274bc
+
+#: Color for marks of type 1 (violet)
+
+#: }}}
+
+#: Advanced {{{
+
+# shell .
+
+#: The shell program to execute. The default value of . means to use
+#: whatever shell is set as the default shell for the current user.
+#: Note that on macOS if you change this, you might need to add
+#: --login to ensure that the shell starts in interactive mode and
+#: reads its startup rc files.
+
+# editor .
+
+#: The console editor to use when editing the kitty config file or
+#: similar tasks. A value of . means to use the environment variables
+#: VISUAL and EDITOR in that order. Note that this environment
+#: variable has to be set not just in your shell startup scripts but
+#: system-wide, otherwise kitty will not see it.
+
+# close_on_child_death no
+
+#: Close the window when the child process (shell) exits. If no (the
+#: default), the terminal will remain open when the child exits as
+#: long as there are still processes outputting to the terminal (for
+#: example disowned or backgrounded processes). If yes, the window
+#: will close as soon as the child process exits. Note that setting it
+#: to yes means that any background processes still using the terminal
+#: can fail silently because their stdout/stderr/stdin no longer work.
+
+# allow_remote_control no
+
+#: Allow other programs to control kitty. If you turn this on other
+#: programs can control all aspects of kitty, including sending text
+#: to kitty windows, opening new windows, closing windows, reading the
+#: content of windows, etc. Note that this even works over ssh
+#: connections. You can chose to either allow any program running
+#: within kitty to control it, with yes or only programs that connect
+#: to the socket specified with the kitty --listen-on command line
+#: option, if you use the value socket-only. The latter is useful if
+#: you want to prevent programs running on a remote computer over ssh
+#: from controlling kitty.
+
+# listen_on none
+
+#: Tell kitty to listen to the specified unix/tcp socket for remote
+#: control connections. Note that this will apply to all kitty
+#: instances. It can be overridden by the kitty --listen-on command
+#: line flag. This option accepts only UNIX sockets, such as
+#: unix:${TEMP}/mykitty or (on Linux) unix:@mykitty. Environment
+#: variables are expanded. If {kitty_pid} is present then it is
+#: replaced by the PID of the kitty process, otherwise the PID of the
+#: kitty process is appended to the value, with a hyphen. This option
+#: is ignored unless you also set allow_remote_control to enable
+#: remote control. See the help for kitty --listen-on for more
+#: details.
+
+# env
+
+#: Specify environment variables to set in all child processes. Note
+#: that environment variables are expanded recursively, so if you
+#: use::
+
+#: env MYVAR1=a
+#: env MYVAR2=${MYVAR1}/${HOME}/b
+
+#: The value of MYVAR2 will be a/<path to home directory>/b.
+
+# update_check_interval 0.0
+
+#: Periodically check if an update to kitty is available. If an update
+#: is found a system notification is displayed informing you of the
+#: available update. The default is to check every 24 hrs, set to zero
+#: to disable.
+
+# startup_session none
+
+#: Path to a session file to use for all kitty instances. Can be
+#: overridden by using the kitty --session command line option for
+#: individual instances. See
+#: https://sw.kovidgoyal.net/kitty/index.html#sessions in the kitty
+#: documentation for details. Note that relative paths are interpreted
+#: with respect to the kitty config directory. Environment variables
+#: in the path are expanded.
+
+# clipboard_control write-clipboard write-primary
+
+#: Allow programs running in kitty to read and write from the
+#: clipboard. You can control exactly which actions are allowed. The
+#: set of possible actions is: write-clipboard read-clipboard write-
+#: primary read-primary. You can additionally specify no-append to
+#: disable kitty's protocol extension for clipboard concatenation. The
+#: default is to allow writing to the clipboard and primary selection
+#: with concatenation enabled. Note that enabling the read
+#: functionality is a security risk as it means that any program, even
+#: one running on a remote server via SSH can read your clipboard.
+
+# allow_hyperlinks yes
+
+#: Process hyperlink (OSC 8) escape sequences. If disabled OSC 8
+#: escape sequences are ignored. Otherwise they become clickable
+#: links, that you can click by holding down ctrl+shift and clicking
+#: with the mouse. The special value of ``ask`` means that kitty will
+#: ask before opening the link.
+
+# term xterm-kitty
+
+#: The value of the TERM environment variable to set. Changing this
+#: can break many terminal programs, only change it if you know what
+#: you are doing, not because you read some advice on Stack Overflow
+#: to change it. The TERM variable is used by various programs to get
+#: information about the capabilities and behavior of the terminal. If
+#: you change it, depending on what programs you run, and how
+#: different the terminal you are changing it to is, various things
+#: from key-presses, to colors, to various advanced features may not
+#: work.
+
+#: }}}
+
+#: OS specific tweaks {{{
+
+# wayland_titlebar_color system
+
+#: Change the color of the kitty window's titlebar on Wayland systems
+#: with client side window decorations such as GNOME. A value of
+#: system means to use the default system color, a value of background
+#: means to use the background color of the currently active window
+#: and finally you can use an arbitrary color, such as #12af59 or red.
+
+# macos_titlebar_color system
+
+#: Change the color of the kitty window's titlebar on macOS. A value
+#: of system means to use the default system color, a value of
+#: background means to use the background color of the currently
+#: active window and finally you can use an arbitrary color, such as
+#: #12af59 or red. WARNING: This option works by using a hack, as
+#: there is no proper Cocoa API for it. It sets the background color
+#: of the entire window and makes the titlebar transparent. As such it
+#: is incompatible with background_opacity. If you want to use both,
+#: you are probably better off just hiding the titlebar with
+#: hide_window_decorations.
+
+# macos_option_as_alt no
+
+#: Use the option key as an alt key. With this set to no, kitty will
+#: use the macOS native Option+Key = unicode character behavior. This
+#: will break any Alt+key keyboard shortcuts in your terminal
+#: programs, but you can use the macOS unicode input technique. You
+#: can use the values: left, right, or both to use only the left,
+#: right or both Option keys as Alt, instead.
+
+# macos_hide_from_tasks no
+
+#: Hide the kitty window from running tasks (⌘+Tab) on macOS.
+
+# macos_quit_when_last_window_closed no
+
+#: Have kitty quit when all the top-level windows are closed. By
+#: default, kitty will stay running, even with no open windows, as is
+#: the expected behavior on macOS.
+
+# macos_window_resizable yes
+
+#: Disable this if you want kitty top-level (OS) windows to not be
+#: resizable on macOS.
+
+# macos_thicken_font 0
+
+#: Draw an extra border around the font with the given width, to
+#: increase legibility at small font sizes. For example, a value of
+#: 0.75 will result in rendering that looks similar to sub-pixel
+#: antialiasing at common font sizes.
+
+# macos_traditional_fullscreen no
+
+#: Use the traditional full-screen transition, that is faster, but
+#: less pretty.
+
+# macos_show_window_title_in all
+
+#: Show or hide the window title in the macOS window or menu-bar. A
+#: value of window will show the title of the currently active window
+#: at the top of the macOS window. A value of menubar will show the
+#: title of the currently active window in the macOS menu-bar, making
+#: use of otherwise wasted space. all will show the title everywhere
+#: and none hides the title in the window and the menu-bar.
+
+# macos_custom_beam_cursor no
+
+#: Enable/disable custom mouse cursor for macOS that is easier to see
+#: on both light and dark backgrounds. WARNING: this might make your
+#: mouse cursor invisible on dual GPU machines.
+
+# linux_display_server auto
+
+#: Choose between Wayland and X11 backends. By default, an appropriate
+#: backend based on the system state is chosen automatically. Set it
+#: to x11 or wayland to force the choice.
+
+#: }}}
+
+#: Keyboard shortcuts {{{
+
+#: Keys are identified simply by their lowercase unicode characters.
+#: For example: ``a`` for the A key, ``[`` for the left square bracket
+#: key, etc. For functional keys, such as ``Enter or Escape`` the
+#: names are present at https://sw.kovidgoyal.net/kitty/keyboard-
+#: protocol.html#functional-key-definitions. For a list of modifier
+#: names, see: GLFW mods
+#: <https://www.glfw.org/docs/latest/group__mods.html>
+
+#: On Linux you can also use XKB key names to bind keys that are not
+#: supported by GLFW. See XKB keys
+#: <https://github.com/xkbcommon/libxkbcommon/blob/master/xkbcommon/xkbcommon-
+#: keysyms.h> for a list of key names. The name to use is the part
+#: after the XKB_KEY_ prefix. Note that you can only use an XKB key
+#: name for keys that are not known as GLFW keys.
+
+#: Finally, you can use raw system key codes to map keys, again only
+#: for keys that are not known as GLFW keys. To see the system key
+#: code for a key, start kitty with the kitty --debug-keyboard option.
+#: Then kitty will output some debug text for every key event. In that
+#: text look for ``native_code`` the value of that becomes the key
+#: name in the shortcut. For example:
+
+#: .. code-block:: none
+
+#: on_key_input: glfw key: 65 native_code: 0x61 action: PRESS mods: 0x0 text: 'a'
+
+#: Here, the key name for the A key is 0x61 and you can use it with::
+
+#: map ctrl+0x61 something
+
+#: to map ctrl+a to something.
+
+#: You can use the special action no_op to unmap a keyboard shortcut
+#: that is assigned in the default configuration::
+
+#: map kitty_mod+space no_op
+
+#: You can combine multiple actions to be triggered by a single
+#: shortcut, using the syntax below::
+
+#: map key combine <separator> action1 <separator> action2 <separator> action3 ...
+
+#: For example::
+
+#: map kitty_mod+e combine : new_window : next_layout
+
+#: this will create a new window and switch to the next available
+#: layout
+
+#: You can use multi-key shortcuts using the syntax shown below::
+
+#: map key1>key2>key3 action
+
+#: For example::
+
+#: map ctrl+f>2 set_font_size 20
+
+# kitty_mod ctrl+shift
+
+#: The value of kitty_mod is used as the modifier for all default
+#: shortcuts, you can change it in your kitty.conf to change the
+#: modifiers for all the default shortcuts.
+
+# clear_all_shortcuts no
+
+#: You can have kitty remove all shortcut definition seen up to this
+#: point. Useful, for instance, to remove the default shortcuts.
+
+# kitten_alias hints hints --hints-offset=0
+
+#: You can create aliases for kitten names, this allows overriding the
+#: defaults for kitten options and can also be used to shorten
+#: repeated mappings of the same kitten with a specific group of
+#: options. For example, the above alias changes the default value of
+#: kitty +kitten hints --hints-offset to zero for all mappings,
+#: including the builtin ones.
+
+#: Clipboard {{{
+
+# map kitty_mod+c copy_to_clipboard
+
+#: There is also a copy_or_interrupt action that can be optionally
+#: mapped to Ctrl+c. It will copy only if there is a selection and
+#: send an interrupt otherwise. Similarly, copy_and_clear_or_interrupt
+#: will copy and clear the selection or send an interrupt if there is
+#: no selection.
+
+# map kitty_mod+v paste_from_clipboard
+# map kitty_mod+s paste_from_selection
+# map shift+insert paste_from_selection
+# map kitty_mod+o pass_selection_to_program
+
+#: You can also pass the contents of the current selection to any
+#: program using pass_selection_to_program. By default, the system's
+#: open program is used, but you can specify your own, the selection
+#: will be passed as a command line argument to the program, for
+#: example::
+
+#: map kitty_mod+o pass_selection_to_program firefox
+
+#: You can pass the current selection to a terminal program running in
+#: a new kitty window, by using the @selection placeholder::
+
+#: map kitty_mod+y new_window less @selection
+
+#: }}}
+
+#: Scrolling {{{
+
+# map kitty_mod+up scroll_line_up
+# map kitty_mod+k scroll_line_up
+# map kitty_mod+down scroll_line_down
+# map kitty_mod+j scroll_line_down
+map shift+page_up scroll_page_up
+map shift+page_down scroll_page_down
+map shift+home scroll_home
+map shift+end scroll_end
+# map kitty_mod+h show_scrollback
+
+#: You can pipe the contents of the current screen + history buffer as
+#: STDIN to an arbitrary program using the ``launch`` function. For
+#: example, the following opens the scrollback buffer in less in an
+#: overlay window::
+
+#: map f1 launch --stdin-source=@screen_scrollback --stdin-add-formatting --type=overlay less +G -R
+
+#: For more details on piping screen and buffer contents to external
+#: programs, see launch.
+
+#: }}}
+
+#: Window management {{{
+
+# map kitty_mod+enter new_window
+
+#: You can open a new window running an arbitrary program, for
+#: example::
+
+#: map kitty_mod+y launch mutt
+
+#: You can open a new window with the current working directory set to
+#: the working directory of the current window using::
+
+#: map ctrl+alt+enter launch --cwd=current
+
+#: You can open a new window that is allowed to control kitty via the
+#: kitty remote control facility by prefixing the command line with @.
+#: Any programs running in that window will be allowed to control
+#: kitty. For example::
+
+#: map ctrl+enter launch --allow-remote-control some_program
+
+#: You can open a new window next to the currently active window or as
+#: the first window, with::
+
+#: map ctrl+n launch --location=neighbor some_program
+#: map ctrl+f launch --location=first some_program
+
+#: For more details, see launch.
+
+# map kitty_mod+n new_os_window
+
+#: Works like new_window above, except that it opens a top level OS
+#: kitty window. In particular you can use new_os_window_with_cwd to
+#: open a window with the current working directory.
+
+# map kitty_mod+w close_window
+# map kitty_mod+] next_window
+# map kitty_mod+[ previous_window
+# map kitty_mod+f move_window_forward
+# map kitty_mod+b move_window_backward
+# map kitty_mod+` move_window_to_top
+# map kitty_mod+r start_resizing_window
+# map kitty_mod+1 first_window
+# map kitty_mod+2 second_window
+# map kitty_mod+3 third_window
+# map kitty_mod+4 fourth_window
+# map kitty_mod+5 fifth_window
+# map kitty_mod+6 sixth_window
+# map kitty_mod+7 seventh_window
+# map kitty_mod+8 eighth_window
+# map kitty_mod+9 ninth_window
+# map kitty_mod+0 tenth_window
+#: }}}
+
+#: Tab management {{{
+
+# map kitty_mod+right next_tab
+# map kitty_mod+left previous_tab
+# map kitty_mod+t new_tab
+# map kitty_mod+q close_tab
+# map kitty_mod+. move_tab_forward
+# map kitty_mod+, move_tab_backward
+# map kitty_mod+alt+t set_tab_title
+
+#: You can also create shortcuts to go to specific tabs, with 1 being
+#: the first tab, 2 the second tab and -1 being the previously active
+#: tab, and any number larger than the last tab being the last tab::
+
+#: map ctrl+alt+1 goto_tab 1
+#: map ctrl+alt+2 goto_tab 2
+
+#: Just as with new_window above, you can also pass the name of
+#: arbitrary commands to run when using new_tab and use
+#: new_tab_with_cwd. Finally, if you want the new tab to open next to
+#: the current tab rather than at the end of the tabs list, use::
+
+#: map ctrl+t new_tab !neighbor [optional cmd to run]
+#: }}}
+
+#: Layout management {{{
+
+# map kitty_mod+l next_layout
+
+#: You can also create shortcuts to switch to specific layouts::
+
+#: map ctrl+alt+t goto_layout tall
+#: map ctrl+alt+s goto_layout stack
+
+#: Similarly, to switch back to the previous layout::
+
+#: map ctrl+alt+p last_used_layout
+#: }}}
+
+#: Font sizes {{{
+
+#: You can change the font size for all top-level kitty OS windows at
+#: a time or only the current one.
+
+# map kitty_mod+equal change_font_size all +2.0
+# map kitty_mod+plus change_font_size all +2.0
+# map kitty_mod+kp_add change_font_size all +2.0
+# map kitty_mod+minus change_font_size all -2.0
+# map kitty_mod+kp_subtract change_font_size all -2.0
+# map kitty_mod+backspace change_font_size all 0
+
+#: To setup shortcuts for specific font sizes::
+
+#: map kitty_mod+f6 change_font_size all 10.0
+
+#: To setup shortcuts to change only the current OS window's font
+#: size::
+
+#: map kitty_mod+f6 change_font_size current 10.0
+#: }}}
+
+#: Select and act on visible text {{{
+
+#: Use the hints kitten to select text and either pass it to an
+#: external program or insert it into the terminal or copy it to the
+#: clipboard.
+
+# map kitty_mod+e kitten hints
+
+#: Open a currently visible URL using the keyboard. The program used
+#: to open the URL is specified in open_url_with.
+
+# map kitty_mod+p>f kitten hints --type path --program -
+
+#: Select a path/filename and insert it into the terminal. Useful, for
+#: instance to run git commands on a filename output from a previous
+#: git command.
+
+# map kitty_mod+p>shift+f kitten hints --type path
+
+#: Select a path/filename and open it with the default open program.
+
+# map kitty_mod+p>l kitten hints --type line --program -
+
+#: Select a line of text and insert it into the terminal. Use for the
+#: output of things like: ls -1
+
+# map kitty_mod+p>w kitten hints --type word --program -
+
+#: Select words and insert into terminal.
+
+# map kitty_mod+p>h kitten hints --type hash --program -
+
+#: Select something that looks like a hash and insert it into the
+#: terminal. Useful with git, which uses sha1 hashes to identify
+#: commits
+
+# map kitty_mod+p>n kitten hints --type linenum
+
+#: Select something that looks like filename:linenum and open it in
+#: vim at the specified line number.
+
+# map kitty_mod+p>y kitten hints --type hyperlink
+
+#: Select a hyperlink (i.e. a URL that has been marked as such by the
+#: terminal program, for example, by ls --hyperlink=auto).
+
+
+#: The hints kitten has many more modes of operation that you can map
+#: to different shortcuts. For a full description see kittens/hints.
+#: }}}
+
+#: Miscellaneous {{{
+
+# map kitty_mod+f11 toggle_fullscreen
+# map kitty_mod+f10 toggle_maximized
+# map kitty_mod+u kitten unicode_input
+# map kitty_mod+f2 edit_config_file
+# map kitty_mod+escape kitty_shell window
+
+#: Open the kitty shell in a new window/tab/overlay/os_window to
+#: control kitty using commands.
+
+# map kitty_mod+a>m set_background_opacity +0.1
+# map kitty_mod+a>l set_background_opacity -0.1
+# map kitty_mod+a>1 set_background_opacity 1
+# map kitty_mod+a>d set_background_opacity default
+# map kitty_mod+delete clear_terminal reset active
+
+#: You can create shortcuts to clear/reset the terminal. For example::
+
+#: # Reset the terminal
+#: map kitty_mod+f9 clear_terminal reset active
+#: # Clear the terminal screen by erasing all contents
+#: map kitty_mod+f10 clear_terminal clear active
+#: # Clear the terminal scrollback by erasing it
+#: map kitty_mod+f11 clear_terminal scrollback active
+#: # Scroll the contents of the screen into the scrollback
+#: map kitty_mod+f12 clear_terminal scroll active
+
+#: If you want to operate on all windows instead of just the current
+#: one, use all instead of active.
+
+#: It is also possible to remap Ctrl+L to both scroll the current
+#: screen contents into the scrollback buffer and clear the screen,
+#: instead of just clearing the screen::
+
+#: map ctrl+l combine : clear_terminal scroll active : send_text normal,application \x0c
+
+
+#: You can tell kitty to send arbitrary (UTF-8) encoded text to the
+#: client program when pressing specified shortcut keys. For example::
+
+#: map ctrl+alt+a send_text all Special text
+
+#: This will send "Special text" when you press the ctrl+alt+a key
+#: combination. The text to be sent is a python string literal so you
+#: can use escapes like \x1b to send control codes or \u21fb to send
+#: unicode characters (or you can just input the unicode characters
+#: directly as UTF-8 text). The first argument to send_text is the
+#: keyboard modes in which to activate the shortcut. The possible
+#: values are normal or application or kitty or a comma separated
+#: combination of them. The special keyword all means all modes. The
+#: modes normal and application refer to the DECCKM cursor key mode
+#: for terminals, and kitty refers to the special kitty extended
+#: keyboard protocol.
+
+#: Another example, that outputs a word and then moves the cursor to
+#: the start of the line (same as pressing the Home key)::
+
+#: map ctrl+alt+a send_text normal Word\x1b[H
+#: map ctrl+alt+a send_text application Word\x1bOH
+
+#: }}}
+
+# }}}
diff --git a/tw/services/files/mimeapps.list b/tw/services/files/mimeapps.list
new file mode 100644
index 00000000..6cbf5ce9
--- /dev/null
+++ b/tw/services/files/mimeapps.list
@@ -0,0 +1,57 @@
+[Default Applications]
+inode/directory=ranger.desktop
+x-scheme-handler/http=icecat.desktop
+x-scheme-handler/https=icecat.desktop
+x-scheme-handler/chrome=icecat.desktop
+x-scheme-handler/about=icecat.desktop
+x-scheme-handler/unknown=icecat.desktop
+x-scheme-handler/mailto=aerc.desktop
+application/x-extension-htm=icecat.desktop
+application/x-extension-html=icecat.desktop
+application/x-extension-shtml=icecat.desktop
+application/xhtml+xml=icecat.desktop
+application/x-extension-xhtml=icecat.desktop
+application/x-extension-xht=icecat.desktop
+application/octet-stream=emacsclient.desktop
+text/html=icecat.desktop
+text/xml=icecat.desktop
+text/plain=emacsclient.desktop
+text/english=emacsclient.desktop
+text/x-makefile=emacsclient.desktop
+text/x-c++hdr=emacsclient.desktop
+text/x-c++src=emacsclient.desktop
+text/x-chdr=emacsclient.desktop
+text/x-java=emacsclient.desktop
+text/x-moc=emacsclient.desktop
+text/x-pascal=emacsclient.desktop
+text/x-tcl=emacsclient.desktop
+text/x-tex=emacsclient.desktop
+text/x-c=emacsclient.desktop
+text/x-c++=emacsclient.desktop
+application/x-shellscript=emacsclient.desktop
+image/bmp=imv.desktop
+image/gif=imv.desktop
+image/jpeg=imv.desktop
+image/jpg=imv.desktop
+image/pjpeg=imv.desktop
+image/png=imv.desktop
+image/tiff=imv.desktop
+image/x-bmp=imv.desktop
+image/x-pcx=imv.desktop
+image/x-png=imv.desktop
+image/x-portable-anymap=imv.desktop
+image/x-portable-bitmap=imv.desktop
+image/x-portable-graymap=imv.desktop
+image/x-portable-pixmap=imv.desktop
+image/x-tga=imv.desktop
+image/x-xbitmap=imv.desktop
+image/heif=imv.desktop
+image/webp=imv.desktop
+application/pdf=org.pwmt.zathura-pdf-poppler.desktop
+application/postscript=org.pwmt.zathura-ps.desktop
+application/eps=org.pwmt.zathura-ps.desktop
+application/x-eps=org.pwmt.zathura-ps.desktop
+image/eps=org.pwmt.zathura-ps.desktop
+image/x-eps=org.pwmt.zathura-ps.desktop
+
+[Added Associations]
diff --git a/tw/services/files/newsboat.conf b/tw/services/files/newsboat.conf
new file mode 100644
index 00000000..7959e138
--- /dev/null
+++ b/tw/services/files/newsboat.conf
@@ -0,0 +1,29 @@
+# newsboat config -*- conf-space -*-
+# https://newsboat.org/releases/2.29/docs/newsboat.html#_newsboat_configuration_commands
+# Vim-like keys.
+bind-key j down
+bind-key k up
+bind-key J next-feed articlelist
+bind-key K prev-feed articlelist
+
+# Reload feeds on startup, in parallel.
+auto-reload yes
+reload-threads 100
+
+text-width 72 # wrap rendered HTML at 72 cols (or terminal width, if smaller)
+scrolloff 2 # show 2 lines above/below selected line
+show-keymap-hint no
+show-title-bar yes
+datetime-format "%e %b %Y" # e.g. " 9 Dec 2022"
+notify-program "dunstify -a newsboat -i newsboat Newsboat"
+notify-format "%d new articles (%n unread articles, %f unread feeds)"
+
+# Sync with Nextcloud News.
+urls-source "ocnews"
+ocnews-url "https://cloud.wilkenfamily.de/"
+ocnews-login "timo"
+ocnews-passwordeval "pass www/nextcloud/timo | head -1"
+ocnews-flag-star "s"
+
+# Catppuccin theme.
+include ~/.config/newsboat/config.catppuccin
diff --git a/tw/services/files/passmenu b/tw/services/files/passmenu
new file mode 100755
index 00000000..9bf7f7e3
--- /dev/null
+++ b/tw/services/files/passmenu
@@ -0,0 +1,93 @@
+#!/bin/sh -eu
+
+usage() {
+ cat << EOF
+$(basename "$0") [-c | -p | -a]
+
+ -c, --clip copy the user-selected password to the clipboard
+ -p, --type-pass auto-type the user-selected password only
+ -o, --type-otp auto-type the user-selected six-digit OTP code
+ -a, --type-all auto-type the user-selected username <tab> password
+ -h, --help show this help message and exit
+ -v, --version show the program version number and exit
+
+Later options override conflicting earlier ones.
+If no option is given, -c/--clip is the default.
+GNU-style combination of short options (e.g. -pa) is not supported.
+
+Abnormal exit codes:
+ 1 no password selected by user
+ 2 invalid command-line argument
+ 3 internal error
+
+(C) 2019-2023 Timo Wilken; MIT Licence.
+Adapted from https://git.zx2c4.com/password-store/tree/contrib/dmenu/passmenu.
+EOF
+}
+
+version() {
+ # Changelog:
+ # 1.0.0 (2019-??-??) initial version
+ # 1.0.1 (2022-07-17) script formatting; throw away passwords asap
+ # 1.0.2 (2022-10-??) remove fix_xdotool; this should be done on login
+ # 1.0.3 (2023-12-11) customise prompt to show what will be typed
+ echo "$(basename "$0") 1.0.3"
+}
+
+password_names() {
+ find "${PASSWORD_STORE_DIR-$HOME/.password-store}" -name '*.gpg' -type f \
+ -printf '%P\n' | sed 's/\.gpg$//'
+}
+
+extract_key() {
+ sed -rn "s/^$1:\\s+(.+)\$/\\1/p"
+}
+
+type_stdin() {
+ tr -d '\n' | xdotool getactivewindow type --clearmodifiers --file -
+}
+
+## Command-line arguments
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -o|--type-otp) mode=type-otp prompt=OTP; shift;;
+ -p|--type-pass) mode=type-pass prompt=Password; shift;;
+ -a|--type-all) mode=type-all prompt=Login; shift;;
+ -c|--clip) mode=clip prompt=Copy; shift;;
+ -h|--help) usage; exit;;
+ -v|--version) version; exit;;
+
+ *)
+ echo "$(basename "$0"): unknown option: $1" >&2
+ usage >&2
+ exit 2;;
+ esac
+done
+: "${mode=clip}" "${prompt=Copy}"
+
+## Password selection menu
+password_name=$(password_names | rofi -dmenu -i -p "$prompt" -no-custom)
+[ -n "$password_name" ] || exit 1
+
+## Password typing
+case "$mode" in
+ clip)
+ # Suppress "copied ... to clipboard" notice on stdout.
+ pass show --clip "$password_name" > /dev/null;;
+
+ type-otp)
+ pass otp "$password_name" | type_stdin;;
+
+ type-pass|type-all)
+ entry=$(pass show "$password_name")
+ if [ "$mode" = type-all ]; then
+ echo "$entry" | extract_key username | type_stdin
+ xdotool getactivewindow key Tab
+ fi
+ echo "$entry" | head -1 | type_stdin
+ unset entry;;
+
+ *)
+ echo "$(basename "$0"): internal error: unknown mode: $mode" >&2
+ exit 3;;
+esac
diff --git a/tw/services/files/picom.conf b/tw/services/files/picom.conf
new file mode 100644
index 00000000..a7d86571
--- /dev/null
+++ b/tw/services/files/picom.conf
@@ -0,0 +1,304 @@
+#
+# ~/.config/picom.conf -- Picom compositor configuration
+#
+
+## Backend
+
+# Backend to use: "xrender" or "glx".
+# GLX backend is typically much faster but depends on a sane driver.
+backend: "glx";
+
+## GLX backend
+
+glx-no-stencil: true;
+
+# GLX backend: Copy unmodified regions from front buffer instead of redrawing
+# them all. My tests with nvidia-drivers show a 10% decrease in performance
+# when the whole screen is modified, but a 20% increase when only 1/4 is. My
+# tests on nouveau show terrible slowdown.
+glx-copy-from-front: false;
+
+# GLX backend: Avoid rebinding pixmap on window damage. Probably could improve
+# performance on rapid window content changes, but is known to break things on
+# some drivers (LLVMpipe). Recommended if it works.
+glx-no-rebind-pixmap: true;
+
+## Shadows
+
+# Enabled client-side shadows on windows.
+shadow: false;
+# The blur radius for shadows. (default 12)
+shadow-radius: 25;
+# The left offset for shadows. (default -15)
+shadow-offset-x: -25;
+# The top offset for shadows. (default -15)
+shadow-offset-y: -25;
+# The translucency for shadows. (default .75)
+shadow-opacity: .3;
+
+# Set if you want different colour shadows
+shadow-red: 0.03;
+shadow-green: 0.03;
+shadow-blue: 0.04;
+
+# The shadow exclude options are helpful if you have shadows enabled. Due to
+# the way compton draws its shadows, certain applications will have visual
+# glitches (most applications are fine, only apps that do weird things with
+# xshapes or argb are affected). This list includes all the affected apps I
+# found in my testing. The "! name~=''" part excludes shadows on any "Unknown"
+# windows, this prevents a visual glitch with the XFWM alt tab switcher.
+# From "man picom", Format of Conditions: WM_CLASS = class_i, class_g
+shadow-exclude: [
+# "! name~=''",
+# "name = 'Notification'",
+# "name = 'Plank'",
+# "name = 'Docky'",
+# "name = 'Kupfer'",
+# "name = 'xfce4-notifyd'",
+# "name *= 'VLC'",
+ "name *= 'mpv'",
+ "class_g = 'ffplay'",
+# "name *= 'compton'",
+# "name *= 'Chromium'",
+# "name *= 'Chrome'",
+ "class_g ?= 'Firefox'",
+# "class_g = 'Conky'",
+ "_NET_WM_STATE@:32a *= '_NET_WM_STATE_STICKY'",
+ "_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'",
+ "_GTK_FRAME_EXTENTS@:c",
+ "class_g ?= 'ulauncher'",
+ "class_g ?= 'tray'",
+ "class_g ?= 'i3-frame'",
+# "class_g ?= 'Notify-osd'",
+# "class_g ?= 'Cairo-dock'",
+# "class_g ?= 'Xfce4-notifyd'",
+# "class_g ?= 'Xfce4-power-manager'"
+ "class_g ?= 'Xfce4-screenshooter'",
+ "class_g ?= 'peek'"
+];
+
+# Avoid drawing shadow on all shaped windows (c.f. --detect-rounded-corners)
+shadow-ignore-shaped: false;
+
+## Opacity
+
+inactive-opacity: 1;
+active-opacity: 1;
+frame-opacity: 1;
+inactive-opacity-override: false;
+
+# Dim inactive windows. (0.0 - 1.0)
+#inactive-dim = 0.1;
+# Do not let dimness adjust based on window opacity.
+#inactive-dim-fixed = true;
+
+# Blur background of transparent windows. Bad performance with X Render
+# backend. GLX backend is preferred.
+blur-method: "dual_kawase";
+blur-strength: 7;
+#blur-method: "kernel";
+# generate kernel using compton-convgen.py --dump-compton gaussian 15 -f sigma=15
+#blur-kernel: "...";
+blur-background: true;
+# Blur background of opaque windows with transparent frames as well.
+blur-background-frame: true;
+# Do not let blur radius adjust based on window opacity.
+blur-background-fixed: true;
+blur-background-exclude: [
+ #"window_type = 'dock'",
+ #"window_type = 'desktop'",
+ "window_type = 'dnd'",
+ "window_type = 'dropdown_menu'",
+ "window_type = 'popup_menu'",
+ #"class_g = 'Rofi'"
+ "class_g ?= 'Xfce4-screenshooter'",
+ # for xfce4-screenshooter triggered from its xfce4-panel button
+ "class_g ?= 'Wrapper-2.0'",
+ "class_g ?= 'peek'"
+];
+
+## Rounded corners
+#corner-radius: 16;
+#rounded-corners-exclude: [
+# "window_type = 'dock'",
+# "window_type = 'desktop'",
+# "class_g ?= 'plasmashell'",
+# "class_g ?= 'krunner'",
+# "class_g ?= 'i3-frame'"
+#];
+
+## Fading
+
+# Fade windows during opacity changes.
+fading: true;
+# The time between steps in a fade in milliseconds. (default 10).
+fade-delta: 5;
+# Opacity change between steps while fading in. (default 0.028).
+fade-in-step: 0.03;
+# Opacity change between steps while fading out. (default 0.03).
+fade-out-step: 0.03;
+# Fade windows in/out when opening/closing
+no-fading-openclose: false;
+
+# Specify a list of conditions of windows that should not be faded.
+fade-exclude: [];
+
+## Other
+
+# Try to detect WM windows and mark them as active.
+mark-wmwin-focused: true;
+# Mark all non-WM but override-redirect windows active (e.g. menus).
+mark-ovredir-focused: true;
+# Use EWMH _NET_WM_ACTIVE_WINDOW to determine which window is focused instead
+# of using FocusIn/Out events. Usually more reliable but depends on a
+# EWMH-compliant WM.
+use-ewmh-active-win: true;
+# Detect rounded corners and treat them as rectangular when
+# --shadow-ignore-shaped is on.
+detect-rounded-corners: true;
+
+# Detect _NET_WM_OPACITY on client windows, useful for window managers not
+# passing _NET_WM_OPACITY of client windows to frame windows. This prevents
+# opacity being ignored for some apps. For example without this enabled my
+# xfce4-notifyd is 100% opacity no matter what.
+detect-client-opacity: true;
+
+# Toggle VSync. See also: --vsync drm/opengl/opengl-oml.
+vsync: true;
+
+# Enable DBE painting mode, intended to use with VSync to (hopefully) eliminate
+# tearing. Reported to have no effect, though.
+dbe: true;
+
+# Unredirect all windows if a full-screen opaque window is detected, to
+# maximize performance for full-screen windows, like games. Known to cause
+# flickering when redirecting/unredirecting windows. paint-on-overlay may make
+# the flickering less obvious.
+unredir-if-possible: true;
+
+# Specify a list of conditions of windows that should always be considered focused.
+focus-exclude: [];
+
+# Use WM_TRANSIENT_FOR to group windows, and consider windows in the same group
+# focused at the same time.
+detect-transient: true;
+# Use WM_CLIENT_LEADER to group windows, and consider windows in the same group
+# focused at the same time. WM_TRANSIENT_FOR has higher priority if
+# --detect-transient is enabled, too.
+detect-client-leader: true;
+
+opacity-rule: [
+ "100:name *?= 'Call'",
+ "100:name *?= 'Conky'",
+ "100:class_g = 'Darktable'",
+ "50:class_g = 'Dmenu'",
+ "100:name *?= 'Event'",
+ "100:class_g = 'Firefox'",
+ "100:class_g = 'GIMP'",
+ "100:name *?= 'Image'",
+ "100:class_g = 'Lazpaint'",
+ "100:class_g = 'Midori'",
+ "100:name *?= 'Minitube'",
+ "83:class_g = 'Mousepad'",
+ "100:name *?= 'MuseScore'",
+ "90:name *?= 'Page Info'",
+ "100:name *?= 'Pale Moon'",
+ "100:name *?= 'Screenshot'",
+ "100:class_g = 'Viewnior'",
+ "100:name *?= 'VirtualBox'",
+ "100:name *?= 'VLC'",
+ "100:class_g = 'mpv'",
+ "100:name *?= 'Write'",
+ "0:_NET_WM_STATE@:32a *= '_NET_WM_STATE_HIDDEN'",
+ "100:_NET_WM_STATE@:32a *= '_NET_WM_STATE_STICKY'"
+];
+
+## Window type settings
+
+wintypes: {
+ # WINDOW_TYPE is one of the 15 window types defined in the EWMH standard:
+ # https://specifications.freedesktop.org/wm-spec/latest/ar01s05.html#idm45550357242464
+ # fade: Fade the particular type of windows.
+ # shadow: Give those windows shadow
+ # opacity: Default opacity for the type of windows.
+ # focus: Controls whether the window of this type is to be always considered
+ # focused. (By default, all window types except "normal" and "dialog" has
+ # this on.)
+ # full-shadow: Controls whether shadow is drawn under the parts of the
+ # window that you normally won't be able to see. Useful when the window
+ # has parts of it transparent, and you want shadows in those areas.
+ # redir-ignore: Controls whether this type of windows should cause screen to
+ # become redirected again after been unredirected. If you have
+ # --unredir-if-possible set, and doesn't want certain window to cause
+ # unnecessary screen redirection, you can set this to true.
+
+ normal: {
+ opacity: 1;
+ };
+
+ dialog: {
+ opacity: 1;
+ };
+
+ # On-screen notification
+ notify: {
+ opacity: 1;
+ };
+
+ tooltip: {
+ opacity: .85;
+ };
+
+ # Desktop window with icons etc.
+ desktop: {
+ };
+
+ # Persistent dock, usually on top of all other windows
+ dock: {
+ opacity: 1;
+ };
+
+ # Window representing something being dragged
+ dnd: {
+ opacity: .85;
+ shadow: true;
+ full-shadow: true;
+ };
+
+ # Menu "torn off" from another window
+ menu: {
+ opacity: 1;
+ };
+
+ # Toolbar "torn off" from another window
+ toolbar: {
+ opacity: 1;
+ };
+
+ # A menu from a right click
+ popup_menu: {
+ opacity: 1;
+ };
+
+ # A menu from a menu bar
+ dropdown_menu: {
+ opacity: 1;
+ };
+
+ # The pop-up/drop-down from a combo box
+ combo: {
+ opacity: 1;
+ };
+
+ # A starting application's splash screen
+ splash: {
+ opacity: 1;
+ };
+
+ # A small persistent utility window, such as a palette or toolbox
+ utility: {
+ };
+
+ unknown: {
+ };
+};
diff --git a/tw/services/files/rofi.rasi b/tw/services/files/rofi.rasi
new file mode 100644
index 00000000..83363c14
--- /dev/null
+++ b/tw/services/files/rofi.rasi
@@ -0,0 +1,41 @@
+/* -*- mode: css -*- */
+configuration {
+ modi: "combi,windowcd,calc";
+ combi-modi: "window,drun,session:sessionmenu,ssh,run";
+ terminal: "rofi-sensible-terminal";
+ icon-theme: "Papirus-Dark";
+ drun-show-actions: true; /* show actions defined in .desktop files as well */
+ combi-display-format: "{text}"; /* hide {mode} prefix */
+ show-icons: true;
+ disable-history: false;
+ case-sensitive: false;
+ cycle: true;
+ matching: "normal";
+ scroll-method: 0;
+ /* Needed by Catppuccin theme: */
+ sidebar-mode: true;
+ hide-scrollbar: true;
+ location: 0;
+ /* End of theme-related config. */
+ display-window: "Windows";
+ display-windowcd: "Workspace";
+ display-run: "Run";
+ display-ssh: "SSH";
+ display-drun: "Desktop";
+ display-combi: "Combined";
+ display-calc: "Calculator";
+ display-session: "Session";
+ display-keys: "Keys";
+ display-filebrowser: "Files";
+ display-emoji: "Emoji";
+ timeout {
+ action: "kb-cancel";
+ delay: 0;
+ }
+ filebrowser {
+ directories-first: true;
+ sorting-method: "name";
+ }
+}
+
+@theme "catppuccin"
diff --git a/tw/services/files/sessionmenu b/tw/services/files/sessionmenu
new file mode 100755
index 00000000..91839d8e
--- /dev/null
+++ b/tw/services/files/sessionmenu
@@ -0,0 +1,38 @@
+#!/usr/bin/env -S guile --no-auto-compile
+!#
+(use-modules (ice-9 format)
+ (ice-9 match)
+ (ice-9 ports))
+
+(define (call-command/stderr . command)
+ "Call COMMAND using `system*', but redirect all output to stderr."
+ (with-output-to-port (current-error-port)
+ (lambda () (apply system* command))))
+
+(match (command-line)
+ ((_ "quit")
+ (exit 0))
+ ((_ "Reload i3 configuration")
+ (call-command/stderr "i3-msg" "reload"))
+ ((_ "Sleep")
+ ;; "screenlock" blocks until unlocked by the user, so run in background.
+ ;; TODO: write "mem" to /sys/power/state, but it's owned by root.
+ (let ((locker (spawn "screenlock" '("screenlock")))
+ (sleepfile (open-file "/sys/power/state" "w")))
+ (display "mem" sleepfile)
+ (close sleepfile)
+ (waitpid locker)))
+ ((_ "Log out")
+ (call-command/stderr "i3-msg" "exit"))
+ ((_ "Shutdown")
+ (call-command/stderr "sudo" "-n" "/run/current-system/profile/sbin/shutdown"))
+ ((_ "Reboot")
+ (call-command/stderr "sudo" "-n" "/run/current-system/profile/sbin/reboot"))
+ (_
+ (format #t "\0~a\x1f~a~%~{~a\0icon\x1f~a~%~}"
+ "prompt" "Session"
+ '("Reload i3 configuration" "reload"
+ "Sleep" "system-suspend"
+ "Log out" "system-log-out"
+ "Shutdown" "system-shutdown"
+ "Reboot" "system-reboot"))))
diff --git a/tw/services/files/vdirsyncer.conf b/tw/services/files/vdirsyncer.conf
new file mode 100644
index 00000000..0745c98f
--- /dev/null
+++ b/tw/services/files/vdirsyncer.conf
@@ -0,0 +1,87 @@
+[general]
+status_path = "~/.local/share/vdirsyncer/status"
+
+# Main contact list from Nextcloud.
+[pair main_contacts]
+a = "nextcloud_main_contacts"
+b = "local_main_contacts"
+metadata = ["displayname", "description"]
+collections = ["from a", "from b"]
+conflict_resolution = "a wins"
+
+[storage local_main_contacts]
+type = "filesystem"
+path = "~/.local/share/vdirsyncer/main-contacts"
+fileext = ".vcf"
+
+[storage nextcloud_main_contacts]
+type = "carddav"
+url = "https://cloud.wilkenfamily.de/remote.php/dav/addressbooks/users/timo/contacts/"
+username.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | awk '/^username: /{print $2}'"]
+password.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | head -1"]
+
+# "Sandbox Contacts" from Nextcloud, mostly for WhatsApp.
+[pair sandbox_contacts]
+a = "nextcloud_sandbox_contacts"
+b = "local_sandbox_contacts"
+metadata = ["displayname", "description"]
+collections = ["from a", "from b"]
+conflict_resolution = "a wins"
+
+[storage local_sandbox_contacts]
+type = "filesystem"
+path = "~/.local/share/vdirsyncer/sandbox-contacts"
+fileext = ".vcf"
+
+[storage nextcloud_sandbox_contacts]
+type = "carddav"
+url = "https://cloud.wilkenfamily.de/remote.php/dav/addressbooks/users/timo/sandbox-contacts/"
+username.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | awk '/^username: /{print $2}'"]
+password.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | head -1"]
+
+# Calendar from Nextcloud.
+[pair calendars]
+a = "nextcloud_calendars"
+b = "local_calendars"
+metadata = ["color", "displayname", "description", "order"]
+collections = ["from a", "from b"]
+conflict_resolution = "a wins"
+
+[storage local_calendars]
+type = "filesystem"
+path = "~/.local/share/vdirsyncer/calendars"
+fileext = ".ics"
+
+[storage nextcloud_calendars]
+type = "caldav"
+url = "https://cloud.wilkenfamily.de/remote.php/dav/principals/users/timo/"
+username.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | awk '/^username: /{print $2}'"]
+password.fetch = ["shell", "~/.guix-home/profile/bin/pass www/nextcloud/timo | head -1"]
+
+# Work Indico calendar (read-only).
+[pair indico]
+a = "indico_calendar"
+b = "local_indico_calendar"
+metadata = []
+collections = null
+conflict_resolution = "a wins"
+
+[storage local_indico_calendar]
+type = "filesystem"
+path = "~/.local/share/vdirsyncer/indico"
+fileext = ".ics"
+
+[storage indico_calendar]
+type = "http"
+# The URL contains an access token, so get it from pass.
+url.fetch = ["shell", "~/.guix-home/profile/bin/pass cern/indico-calendar-url | head -1"]
+
+[storage local_cern_calendar]
+type = "filesystem"
+path = "~/.local/share/vdirsyncer/cern"
+fileext = ".ics"
+
+[storage cern_calendar]
+type = "http"
+# The URL contains an access token, so get it from pass.
+url.fetch = ["shell", "~/.guix-home/profile/bin/pass cern/exol/calendar-url | head -1"]
diff --git a/tw/services/files/vimrc b/tw/services/files/vimrc
new file mode 100644
index 00000000..9c48346f
--- /dev/null
+++ b/tw/services/files/vimrc
@@ -0,0 +1,60 @@
+let mapleader = "\<SPACE>"
+let maplocalleader = "\\"
+
+set showcmd " show command as they are typed
+set showmatch " show matching brackets, briefly jump to matching one
+set matchtime=0 " tenths of a sec to jump to matching brackets
+set showmode " show NORMAL/INSERT/VISUAL indicator
+set ruler " show cursor position
+set number " show line numbers
+set cursorline " highlight current line
+set modeline " check for modelines
+set hlsearch " highlight search results
+set ignorecase " case-insensitive searching
+set smartcase " ...unless query has capital letters
+set incsearch " incremental search
+set gdefault " :s/search/replace/ has g by default
+set magic " use extended regexes
+set wildmenu " autocomplete :commands
+set nolazyredraw " redraw during macro execution etc.
+set foldlevelstart=10 " open most folds by default
+set foldmethod=syntax " (or marker, manual, expr, syntax, diff)
+set noequalalways " don't resize all windows when closing/opening another
+set eadirection=both " 'equalalways' applies horizontally and vertically
+
+set expandtab " tabs are actually spaces
+set tabstop=4 " tab width
+set softtabstop=4 " when reading a '\t', it will be this wide
+set shiftwidth=4 " autoindent tab width
+set list " show whitespace with characters in 'listchars'
+" possible chars: eol, tab, space, trail, extends, precedes, conceal, nbsp
+set listchars=tab:>\ ,trail:_ " highlight tabs and trailing spaces specially
+set showbreak=>\ " character to show at start of wrapped lines
+set nojoinspaces " don't add two spaces after [.!?]
+
+set scrolloff=5 " always show these many lines around the current one
+set sidescrolloff=15 " always show these many columns around the current one
+set colorcolumn=80 " highlight 80th column
+set textwidth=79 " break long lines on whitespace when inserted
+set linebreak " wrap by words (affects display only)
+set breakindent " wrapped lines have same virtual indent as first line
+set formatoptions=tcqn2 " preserve textwidth, comments, allow gq, numbered
+ " lists, second line indent
+
+set mouse=a " use mouse in terminal; press shift to disable temporarily
+set guicursor=n-v-c:block,i-ci-ve:ver25,r-cr:hor20,o:hor50
+ \,a:blinkwait700-blinkoff400-blinkon250-Cursor/lCursor
+ \,sm:block-blinkwait175-blinkoff150-blinkon175
+
+" Fix colours in kitty -- see :help xterm-true-color.
+set termguicolors
+let &t_8f = "\<Esc>[38;2;%lu;%lu;%lum"
+let &t_8b = "\<Esc>[48;2;%lu;%lu;%lum"
+
+" Enable auto-highlighting and auto-detection of file types and indentation rules.
+filetype on
+filetype plugin on
+filetype indent on
+syntax on
+
+source ~/.vim/catppuccin.vim
diff --git a/tw/services/files/volume b/tw/services/files/volume
new file mode 100755
index 00000000..96398553
--- /dev/null
+++ b/tw/services/files/volume
@@ -0,0 +1,60 @@
+#!/usr/bin/env -S guile --no-auto-compile
+!#
+(use-modules (ice-9 format)
+ (ice-9 match)
+ (ice-9 popen)
+ (ice-9 regex)
+ (ice-9 textual-ports))
+
+(define (read-from-pactl . args)
+ (let* ((port (apply open-pipe* OPEN_READ "pactl" args))
+ (output (get-string-all port)))
+ (close-pipe port)
+ (string-trim-right output #\newline)))
+
+(define %default-sink
+ (read-from-pactl "get-default-sink"))
+
+(define* (sink-muted? #:optional (sink %default-sink))
+ (match (read-from-pactl "get-sink-mute" sink)
+ ("Mute: yes" #t)
+ ("Mute: no" #f)
+ (output (error "Unknown `pactl get-sink-mute' output" output))))
+
+(define* (sink-volume #:optional (sink %default-sink))
+ (match-let ((`#(,match (,start . ,end))
+ (string-match "[0-9]+%" (read-from-pactl "get-sink-volume" sink))))
+ (string->number (substring match start (1- end))))) ; trim trailing "%"
+
+(define* (set-sink-mute! #:optional (state "toggle") (sink %default-sink))
+ (status:exit-val
+ (system* "pactl" "set-sink-mute" sink state)))
+
+(define* (increment-sink-volume! increment-percent #:optional (sink %default-sink))
+ (status:exit-val
+ (system* "pactl" "set-sink-volume" sink
+ ;; A percentage with a sign is an increment.
+ (format #f "~@d%" increment-percent))))
+
+(define* (notify #:optional (muted? (sink-muted?)) (volume (sink-volume)))
+ (status:exit-val
+ (system* "dunstify" "-a" "volume" "-u" "low"
+ "-i" (cond (muted? "audio-volume-muted")
+ ((< volume 50) "audio-volume-low")
+ (#t "audio-volume-high"))
+ "-h" "string:x-canonical-private-synchronous:volume"
+ "-h" (format #f "int:value:~d" (if muted? 0 volume))
+ (if muted? "Volume muted" "Volume"))))
+
+(match (cdr (program-arguments))
+ (() 0) ; notify only
+ (("mute") (set-sink-mute! "true"))
+ (("unmute") (set-sink-mute! "false"))
+ (("toggle-mute") (set-sink-mute! "toggle"))
+ (((? string->number increment-percent))
+ (and (set-sink-mute! "false")
+ (increment-sink-volume!
+ (string->number increment-percent))))
+ (args (error "Could not parse command-line arguments:" args)))
+
+(notify)
diff --git a/tw/services/files/zathurarc b/tw/services/files/zathurarc
new file mode 100644
index 00000000..06a9351f
--- /dev/null
+++ b/tw/services/files/zathurarc
@@ -0,0 +1,32 @@
+# see man zathurarc -*- mode: conf-space -*-
+
+# [c]ommand line, [s]tatus bar, [v]ertical/[h]orizontal scrollbar
+set guioptions s
+set recolor true
+set recolor-keephue false
+set recolor-reverse-video true
+set render-loading true
+
+set scroll-hstep -1
+set scroll-step 40
+set scroll-full-overlap 0.0
+set scroll-page-aware true
+
+set window-title-basename true
+set window-title-home-tilde true
+set window-title-page false
+set statusbar-basename false
+set statusbar-home-tilde true
+
+set selection-clipboard clipboard
+set selection-notification true
+
+# Completion
+set show-directories true
+set show-hidden true
+set show-recent 10
+
+# Theme
+set font "Hermit 10"
+set page-padding 0 # px between pages
+include catppuccin