fix a lot of issues in neomutt

This commit is contained in:
Ray Andrew 2025-12-03 00:35:13 -06:00
parent 4f39c80e25
commit 07b68562d3
Signed by: rayandrew
SSH key fingerprint: SHA256:XYrYrxF0Z3A72n8P/p6mqPRNQZT22F88XcLsG+kX4xw
14 changed files with 271 additions and 41 deletions

65
bin/mailcap-open Executable file
View file

@ -0,0 +1,65 @@
#!/bin/bash
# Open file with mailcap viewer selected via fzf
export PATH="/etc/profiles/per-user/$USER/bin:/run/current-system/sw/bin:$PATH"
# Handle piped input or file argument
if [[ -n "$1" ]]; then
file="$1"
else
# Read from stdin to temp file
tmpfile=$(mktemp)
cat > "$tmpfile"
mime=$(file --mime-type -b "$tmpfile")
# Add extension based on mime type
case "$mime" in
application/pdf) ext=".pdf" ;;
image/png) ext=".png" ;;
image/jpeg) ext=".jpg" ;;
image/gif) ext=".gif" ;;
text/html) ext=".html" ;;
text/plain) ext=".txt" ;;
*) ext="" ;;
esac
if [[ -n "$ext" ]]; then
mv "$tmpfile" "${tmpfile}${ext}"
file="${tmpfile}${ext}"
else
file="$tmpfile"
fi
fi
mime=$(file --mime-type -b "$file")
# Get matching viewers based on mime type
viewers=()
viewers+=("open (default app)")
case "$mime" in
application/pdf)
viewers+=("zathura")
;;
image/*)
viewers+=("chafa (terminal)")
;;
text/html)
viewers+=("w3m (browser)")
viewers+=("less (text)")
;;
text/*)
viewers+=("less")
;;
esac
# Select with fzf
selected=$(printf '%s\n' "${viewers[@]}" | fzf --prompt="Open with: " --height=10)
case "$selected" in
"open (default app)") open "$file" ;;
"chafa (terminal)") chafa "$file"; read -n 1 -s -r -p "Press any key..." ;;
"zathura") zathura "$file" ;;
"w3m (browser)") w3m -T text/html "$file" ;;
"less"*) less "$file" ;;
esac

31
bin/open-attachment Executable file
View file

@ -0,0 +1,31 @@
#!/bin/bash
# Open attachment with correct extension based on mime type
tmpfile=$(mktemp)
cat > "$tmpfile"
mime=$(file --mime-type -b "$tmpfile")
case "$mime" in
application/pdf) ext=".pdf" ;;
image/png) ext=".png" ;;
image/jpeg) ext=".jpg" ;;
image/gif) ext=".gif" ;;
text/html) ext=".html" ;;
text/plain) ext=".txt" ;;
application/zip) ext=".zip" ;;
application/msword) ext=".doc" ;;
application/vnd.openxmlformats-officedocument.wordprocessingml.document) ext=".docx" ;;
application/vnd.ms-excel) ext=".xls" ;;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet) ext=".xlsx" ;;
application/vnd.ms-powerpoint) ext=".ppt" ;;
application/vnd.openxmlformats-officedocument.presentationml.presentation) ext=".pptx" ;;
*) ext="" ;;
esac
if [[ -n "$ext" ]]; then
mv "$tmpfile" "${tmpfile}${ext}"
tmpfile="${tmpfile}${ext}"
fi
open "$tmpfile"

28
bin/open-mail Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
# Open neomutt in Ghostty, or focus existing window
if [[ "$(uname)" == "Darwin" ]]; then
if aerospace list-windows --all | grep -q 'Mail.*Ghostty\|Ghostty.*Mail'; then
aerospace workspace 8
else
aerospace workspace 8
open -na Ghostty --args --title="Mail" -e /etc/profiles/per-user/rayandrew/bin/neomutt
fi
else
# Linux (i3/sway)
if command -v swaymsg &>/dev/null; then
if swaymsg -t get_tree | grep -q '"name": "Mail"'; then
swaymsg workspace 8
else
swaymsg workspace 8
ghostty --title="Mail" -e neomutt &
fi
elif command -v i3-msg &>/dev/null; then
if i3-msg -t get_tree | grep -q '"name": "Mail"'; then
i3-msg workspace 8
else
i3-msg workspace 8
ghostty --title="Mail" -e neomutt &
fi
fi
fi

28
bin/open-music Executable file
View file

@ -0,0 +1,28 @@
#!/bin/bash
# Open spotify_player in Ghostty, or focus existing window
if [[ "$(uname)" == "Darwin" ]]; then
if aerospace list-windows --all | grep -q 'Music.*Ghostty\|Ghostty.*Music'; then
aerospace workspace 7
else
aerospace workspace 7
open -na Ghostty --args --title="Music" -e /etc/profiles/per-user/rayandrew/bin/spotify_player
fi
else
# Linux (i3/sway)
if command -v swaymsg &>/dev/null; then
if swaymsg -t get_tree | grep -q '"name": "Music"'; then
swaymsg workspace 7
else
swaymsg workspace 7
ghostty --title="Music" -e spotify_player &
fi
elif command -v i3-msg &>/dev/null; then
if i3-msg -t get_tree | grep -q '"name": "Music"'; then
i3-msg workspace 7
else
i3-msg workspace 7
ghostty --title="Music" -e spotify_player &
fi
fi
fi

13
bin/path-shim Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
# Shim to run commands with nix paths available
# Usage: path-shim command args...
# Or: path-shim "command with args"
export PATH="/etc/profiles/per-user/$USER/bin:/run/current-system/sw/bin:$PATH"
if [[ $# -eq 1 ]]; then
# Single argument - run it through bash to handle complex commands
exec bash -c "$1"
else
exec "$@"
fi

View file

@ -1,4 +1,5 @@
config-version = 2 config-version = 2
start-at-login = true
after-startup-command = [ after-startup-command = [
"workspace 10", "workspace 10",
@ -9,13 +10,13 @@ persistent-workspaces = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
enable-normalization-flatten-containers = false enable-normalization-flatten-containers = false
enable-normalization-opposite-orientation-for-nested-containers = false enable-normalization-opposite-orientation-for-nested-containers = false
on-focused-monitor-changed = ['move-mouse monitor-lazy-center'] on-focused-monitor-changed = ['move-mouse monitor-lazy-center']
exec-on-workspace-change = ['/bin/bash', '-c', exec-on-workspace-change = ['/Users/rayandrew/dotfiles/bin/path-shim',
'/run/current-system/sw/bin/sketchybar --trigger aerospace_workspace_changed FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE PREV_WORKSPACE=$AEROSPACE_PREV_WORKSPACE && /etc/profiles/per-user/rayandrew/bin/aerospace-scratchpad hook pull-window $AEROSPACE_PREV_WORKSPACE $AEROSPACE_FOCUSED_WORKSPACE' 'sketchybar --trigger aerospace_workspace_changed FOCUSED_WORKSPACE=$AEROSPACE_FOCUSED_WORKSPACE PREV_WORKSPACE=$AEROSPACE_PREV_WORKSPACE && aerospace-scratchpad hook pull-window $AEROSPACE_PREV_WORKSPACE $AEROSPACE_FOCUSED_WORKSPACE'
] ]
on-focus-changed = [ on-focus-changed = [
# 'move-mouse window-lazy-center', # 'move-mouse window-lazy-center',
'exec-and-forget /bin/bash -c /run/current-system/sw/bin/sketchybar --trigger front_app_switched', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger front_app_switched',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger update_windows' 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger update_windows'
] ]
[gaps] [gaps]
@ -28,7 +29,8 @@ on-focus-changed = [
[mode.main.binding] [mode.main.binding]
alt-enter = 'exec-and-forget open -na Ghostty' alt-enter = 'exec-and-forget open -na Ghostty'
alt-m = ['workspace 7', 'exec-and-forget open -na Ghostty --args --title="Music" -e /etc/profiles/per-user/rayandrew/bin/spotify_player'] alt-p = 'exec-and-forget ~/dotfiles/bin/open-music'
alt-m = 'exec-and-forget ~/dotfiles/bin/open-mail'
alt-tab = 'workspace-back-and-forth' alt-tab = 'workspace-back-and-forth'
alt-h = 'focus --boundaries-action wrap-around-the-workspace left' alt-h = 'focus --boundaries-action wrap-around-the-workspace left'
@ -75,14 +77,14 @@ on-focus-changed = [
alt-shift-semicolon = [ alt-shift-semicolon = [
'mode service', 'mode service',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger send_message MESSAGE="SERVICE MODE" HOLD="true"' 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger send_message MESSAGE=SERVICE_MODE HOLD=true'
] ]
alt-r = [ alt-r = [
'mode resize', 'mode resize',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger send_message MESSAGE="RESIZE MODE" HOLD="true"' 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger send_message MESSAGE=RESIZE_MODE HOLD=true'
] ]
cmd-ctrl-1 = "exec-and-forget aerospace-scratchpad show Finder" cmd-ctrl-1 = "exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim aerospace-scratchpad show Finder"
[mode.resize.binding] [mode.resize.binding]
h = 'resize width -50' h = 'resize width -50'
@ -91,18 +93,18 @@ on-focus-changed = [
l = 'resize width +50' l = 'resize width +50'
esc = [ esc = [
'mode main', 'mode main',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger hide_message' 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger hide_message'
] ]
enter = [ enter = [
'mode main', 'mode main',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger hide_message' 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger hide_message'
] ]
[mode.service.binding] [mode.service.binding]
esc = [ esc = [
'reload-config', 'reload-config',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger hide_message', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger hide_message',
'exec-and-forget /run/current-system/sw/bin/sketchybar --reload', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --reload',
'mode main', 'mode main',
] ]
r = ['flatten-workspace-tree', 'mode main'] r = ['flatten-workspace-tree', 'mode main']
@ -117,14 +119,14 @@ on-focus-changed = [
p = [ p = [
'exec-and-forget ~/dotfiles/bin/presentation-mode on', 'exec-and-forget ~/dotfiles/bin/presentation-mode on',
'exec-and-forget /run/current-system/sw/bin/sketchybar --bar height=0', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --bar height=0',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger hide_message', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger hide_message',
'mode main' 'mode main'
] ]
shift-p = [ shift-p = [
'exec-and-forget ~/dotfiles/bin/presentation-mode off', 'exec-and-forget ~/dotfiles/bin/presentation-mode off',
'exec-and-forget /run/current-system/sw/bin/sketchybar --bar height=30', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --bar height=30',
'exec-and-forget /run/current-system/sw/bin/sketchybar --trigger hide_message', 'exec-and-forget /Users/rayandrew/dotfiles/bin/path-shim sketchybar --trigger hide_message',
'mode main' 'mode main'
] ]

View file

@ -56,3 +56,5 @@ theme = noctis-azureus
keybind = all:ctrl+shift+period=text:\x1b\x1f\x4c\x23\x1f keybind = all:ctrl+shift+period=text:\x1b\x1f\x4c\x23\x1f
keybind = shift+enter=text:\n keybind = shift+enter=text:\n
keybind = ctrl+left_bracket=text:\x1b
# keybind = ctrl+a=text:\x01

View file

@ -1,28 +1,29 @@
# Mailcap - MIME type handlers for neomutt and other mail clients # Mailcap - MIME type handlers for neomutt and other mail clients
# Using path-shim to ensure nix paths are available
# HTML - terminal inline view (for auto_view) # HTML - terminal inline view (for auto_view)
text/html; w3m -dump -T text/html %s; copiousoutput text/html; ~/dotfiles/bin/path-shim w3m -dump -T text/html %s; copiousoutput
# HTML - open in browser # HTML - open in browser
text/html; open %s; nametemplate=%s.html text/html; open %s; nametemplate=%s.html
# Plain text # Plain text
text/plain; TERM=xterm-256color less %s text/plain; ~/dotfiles/bin/path-shim TERM=xterm-256color less %s
text/*; TERM=xterm-256color less %s text/*; ~/dotfiles/bin/path-shim TERM=xterm-256color less %s
# PDF # PDF
application/pdf; zathura %s application/pdf; ~/dotfiles/bin/path-shim zathura %s
application/pdf; open %s application/pdf; open %s
# Images - terminal (kitty protocol works in ghostty) # Images - terminal
image/*; kitten icat --clear %s image/*; ~/dotfiles/bin/path-shim chafa %s; needsterminal
image/*; open %s image/*; open %s
# Video # Video
video/*; iina %s video/*; ~/dotfiles/bin/path-shim iina %s
video/*; open %s video/*; open %s
# Audio # Audio
audio/*; mpv --no-video %s audio/*; ~/dotfiles/bin/path-shim mpv --no-video %s
audio/*; open %s audio/*; open %s
# Microsoft Office - Word # Microsoft Office - Word
@ -38,10 +39,10 @@ application/vnd.ms-powerpoint; open %s
application/vnd.openxmlformats-officedocument.presentationml.presentation; open %s application/vnd.openxmlformats-officedocument.presentationml.presentation; open %s
# Archives - show contents # Archives - show contents
application/zip; unzip -l %s; copiousoutput application/zip; ~/dotfiles/bin/path-shim unzip -l %s; copiousoutput
application/x-tar; tar -tvf %s; copiousoutput application/x-tar; ~/dotfiles/bin/path-shim tar -tvf %s; copiousoutput
application/gzip; tar -tzvf %s; copiousoutput application/gzip; ~/dotfiles/bin/path-shim tar -tzvf %s; copiousoutput
application/x-bzip2; tar -tjvf %s; copiousoutput application/x-bzip2; ~/dotfiles/bin/path-shim tar -tjvf %s; copiousoutput
# Fallback - open with default macOS app # Fallback - open with default macOS app
application/*; open %s application/*; open %s

View file

@ -5,7 +5,7 @@
IMAPAccount personal IMAPAccount personal
Host imap.gmail.com Host imap.gmail.com
User raydreww@gmail.com User raydreww@gmail.com
PassCmd "sops -d --extract '[\"personal\"]' ~/dotfiles/home/email/secrets.yaml" PassCmd "/etc/profiles/per-user/rayandrew/bin/sops -d --extract '[\"personal\"]' ~/dotfiles/home/email/secrets.yaml"
TLSType IMAPS TLSType IMAPS
CertificateFile /etc/ssl/certs/ca-certificates.crt CertificateFile /etc/ssl/certs/ca-certificates.crt

View file

@ -24,7 +24,7 @@ bind pager,browser G bottom-page
# Index # Index
bind index G last-entry bind index G last-entry
bind index g noop bind index,pager g noop
bind index gg first-entry bind index gg first-entry
bind index D delete-message bind index D delete-message
bind index U undelete-message bind index U undelete-message
@ -58,8 +58,13 @@ bind index,pager @ compose-to-sender
bind index,pager D purge-message bind index,pager D purge-message
# Macros # Macros
macro index,pager a ":set confirmappend=no delete=yes\n<tag-prefix><save-message>=Archive\n<sync-mailbox>:set confirmappend=yes delete=ask-yes\n" macro index,pager a ":set confirmappend=no delete=yes\n<tag-prefix><save-message>=Archive\n<sync-mailbox>:set confirmappend=yes delete=ask-yes\n" "Archive"
macro index,pager n "<tag-prefix><clear-flag>N<untag-pattern>.<enter>\n" macro index,pager A ":set confirmappend=no delete=yes\n<tag-prefix><save-message>=Inbox\n<sync-mailbox>:set confirmappend=yes delete=ask-yes\n" "Move to Inbox"
macro attach O "<enter-command>unset wait_key<enter><shell-escape>rm -f /tmp/mutt-attach<enter><save-entry><kill-line>/tmp/mutt-attach<enter>^A" macro index r "<tag-prefix><clear-flag>N<untag-pattern>.<enter><sync-mailbox>" "Mark as read"
macro attach,pager A "|git apply<enter>" "Apply git patch" # bind index \Ca noop
macro index \Ca "<tag-pattern>.<enter>" "Tag all messages"
macro index \Cu "<untag-pattern>.<enter>" "Untag all messages"
macro attach o "<enter-command>unset wait_key<enter><pipe-entry>~/dotfiles/bin/open-attachment<enter>" "Open with default app"
macro attach O "<enter-command>unset wait_key<enter><pipe-entry>~/dotfiles/bin/mailcap-open<enter>" "Open with fzf picker"
macro attach,pager p "|git apply<enter>" "Apply git patch"
macro attach,pager P "|git-apply-patch<enter>" "Apply git patch (interactive)" macro attach,pager P "|git-apply-patch<enter>" "Apply git patch (interactive)"

View file

@ -51,9 +51,9 @@ set sendmail = 'msmtpq --read-envelope-from --read-recipients'
source ~/.config/neomutt/keybinds source ~/.config/neomutt/keybinds
source ~/.config/neomutt/colors source ~/.config/neomutt/colors
# Account switching macros # Account switching macros (g + u/p for uchicago/personal)
macro index,pager <f2> "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/uchicago<enter><change-folder>!<enter>" macro index,pager gu "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/uchicago<enter><change-folder>!<enter>" "Switch to uchicago"
macro index,pager <f3> "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/personal<enter><change-folder>!<enter>" macro index,pager gp "<sync-mailbox><enter-command>source ~/.config/neomutt/accounts/personal<enter><change-folder>!<enter>" "Switch to personal"
# Register accounts for folder hooks # Register accounts for folder hooks
named-mailboxes "p" "~/mail/personal/Inbox" named-mailboxes "p" "~/mail/personal/Inbox"

View file

@ -3,6 +3,7 @@
lib, lib,
config, config,
dots, dots,
xdg-config-dir,
... ...
}: }:
@ -27,6 +28,10 @@
enable = config.custom.email.mbsync; enable = config.custom.email.mbsync;
configFile = "${dots}/config/mbsync/mbsyncrc"; configFile = "${dots}/config/mbsync/mbsyncrc";
frequency = "*:0/1"; frequency = "*:0/1";
extraPackages = with pkgs; [ sops ];
environment = {
SOPS_AGE_KEY_FILE = "${xdg-config-dir}/sops/age/keys.txt";
};
}; };
# Install mail-related packages # Install mail-related packages
@ -41,7 +46,6 @@
++ lib.optionals config.custom.email.neomutt [ ++ lib.optionals config.custom.email.neomutt [
# Wrapper script for neomutt with truecolor support # Wrapper script for neomutt with truecolor support
(writeShellScriptBin "neomutt" '' (writeShellScriptBin "neomutt" ''
export TERMINFO_DIRS="${ncurses}/share/terminfo''${TERMINFO_DIRS:+:$TERMINFO_DIRS}"
exec env TERM=xterm-direct ${neomutt}/bin/neomutt "$@" exec env TERM=xterm-direct ${neomutt}/bin/neomutt "$@"
'') '')
] ]
@ -49,7 +53,7 @@
mailcap mailcap
w3m # HTML rendering w3m # HTML rendering
zathura # PDF viewer zathura # PDF viewer
kitty # for kitten icat chafa # terminal image viewer
]; ];
# Symlink config files # Symlink config files

View file

@ -54,6 +54,7 @@ let
system-font = "Consolas"; system-font = "Consolas";
dots = "/home/${user}/dotfiles"; dots = "/home/${user}/dotfiles";
home-dir = config.home-manager.users.${user}.home.homeDirectory; home-dir = config.home-manager.users.${user}.home.homeDirectory;
xdg-config-dir = config.home-manager.users.${user}.xdg.configHome;
}; };
users.${user} = { users.${user} = {
@ -129,6 +130,7 @@ let
inherit host user system; inherit host user system;
system-font = "SF Mono"; system-font = "SF Mono";
home-dir = config.home-manager.users.${user}.home.homeDirectory; home-dir = config.home-manager.users.${user}.home.homeDirectory;
xdg-config-dir = config.home-manager.users.${user}.xdg.configHome;
dots = "/Users/${user}/dotfiles"; dots = "/Users/${user}/dotfiles";
}; };

View file

@ -96,6 +96,37 @@ in
This is useful for running mailbox indexing tools. This is useful for running mailbox indexing tools.
''; '';
}; };
extraPath = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "/etc/profiles/per-user/myuser/bin" ];
description = ''
Extra paths to add to PATH environment variable for the mbsync service.
Useful for making commands like sops available to PassCmd.
'';
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
example = lib.literalExpression "[ pkgs.sops ]";
description = ''
Extra packages whose bin directories will be added to PATH.
Useful for making commands like sops available to PassCmd.
'';
};
environment = mkOption {
type = types.attrsOf types.str;
default = { };
example = {
SOPS_AGE_KEY_FILE = "/home/user/.config/sops/age/keys.txt";
};
description = ''
Extra environment variables for the mbsync service.
'';
};
}; };
config = mkIf cfg.enable (mkMerge [ config = mkIf cfg.enable (mkMerge [
@ -134,6 +165,11 @@ in
}; };
}) })
# Install extra packages
(mkIf (cfg.extraPackages != [ ]) {
home.packages = cfg.extraPackages;
})
# Darwin-specific: launchd agent # Darwin-specific: launchd agent
(mkIf isDarwin { (mkIf isDarwin {
launchd.agents.mbsync = { launchd.agents.mbsync = {
@ -154,7 +190,20 @@ in
RunAtLoad = true; RunAtLoad = true;
StandardErrorPath = "/tmp/mbsync.err.log"; StandardErrorPath = "/tmp/mbsync.err.log";
StandardOutPath = "/tmp/mbsync.out.log"; StandardOutPath = "/tmp/mbsync.out.log";
}; }
// (lib.optionalAttrs (cfg.extraPath != [ ] || cfg.extraPackages != [ ] || cfg.environment != { }) {
EnvironmentVariables = {
PATH = concatStringsSep ":" (
cfg.extraPath
++ (map (pkg: "${pkg}/bin") cfg.extraPackages)
++ [
"/usr/bin"
"/bin"
]
);
}
// cfg.environment;
});
}; };
}) })
]); ]);