;;; mu4e-draft.el --- Helpers for m4e-compose -*- lexical-binding: t -*- ;; Copyright (C) 2024-2026 Dirk-Jan C. Binnema ;; Author: Dirk-Jan C. Binnema ;; Maintainer: Dirk-Jan C. Binnema ;; This file is not part of GNU Emacs. ;; mu4e 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. ;; mu4e 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 mu4e. If not, see . ;;; Commentary: ;; Implements various helper functions for mu4e-compose. This all ;; look a little convoluted since we need to subvert the gnus/message ;; functions a bit to work with mu4e. (require 'message) (require 'mm-decode) (require 'mu4e-config) (require 'mu4e-helpers) (require 'mu4e-contacts) (require 'mu4e-folders) (require 'mu4e-message) (require 'mu4e-context) (require 'mu4e-window) ;;; Code: (declare-function mu4e-compose-mode "mu4e-compose") (declare-function mu4e "mu4e") (defcustom mu4e-compose-crypto-policy '(encrypt-encrypted-replies sign-encrypted-replies sign-signed-replies) "Policy to control when messages will be signed/encrypted. The value is a list which influence the way draft messages are created. Specifically, it might contain: - `sign-all-messages': Always add a signature. - `sign-new-messages': Add a signature to new message, ie. messages that aren't responses to another message. - `sign-forwarded-messages': Add a signature when forwarding a message - `sign-edited-messages': Add a signature to drafts - `sign-all-replies': Add a signature when responding to another message. - `sign-plain-replies': Add a signature when responding to non-encrypted messages. - `sign-encrypted-replies': Add a signature when responding to encrypted messages. - `sign-signed-replies': Add a signature when responding to signed messages. It should be noted that certain symbols have priorities over one another. So `sign-all-messages' implies `sign-all-replies', which in turn implies `sign-plain-replies'. Adding both to the set, is not a contradiction, but a redundant configuration. All `sign-*' options have a `encrypt-*' analogue." :type '(set :greedy t (const :tag "Sign all messages" sign-all-messages) (const :tag "Encrypt all messages" encrypt-all-messages) (const :tag "Sign new messages" sign-new-messages) (const :tag "Encrypt new messages" encrypt-new-messages) (const :tag "Sign forwarded messages" sign-forwarded-messages) (const :tag "Encrypt forwarded messages" encrypt-forwarded-messages) (const :tag "Sign edited messages" sign-edited-messages) (const :tag "Encrypt edited messages" edited-forwarded-messages) (const :tag "Sign all replies" sign-all-replies) (const :tag "Encrypt all replies" encrypt-all-replies) (const :tag "Sign replies to plain messages" sign-plain-replies) (const :tag "Encrypt replies to plain messages" encrypt-plain-replies) (const :tag "Sign replies to encrypted messages" sign-encrypted-replies) (const :tag "Encrypt replies to encrypted messages" encrypt-encrypted-replies) (const :tag "Sign replies to signed messages" sign-signed-replies) (const :tag "Encrypt replies to signed messages" encrypt-signed-replies)) :group 'mu4e-compose) ;;; Crypto (defun mu4e--prepare-crypto (parent compose-type) "Possibly encrypt or sign a message based on PARENT and COMPOSE-TYPE. See `mu4e-compose-crypto-policy' for more details." (let* ((encrypted-p (and parent (memq 'encrypted (mu4e-message-field parent :flags)))) (signed-p (and parent (memq 'signed (mu4e-message-field parent :flags)))) (encrypt (or (memq 'encrypt-all-messages mu4e-compose-crypto-policy) (and (memq 'encrypt-new-messages mu4e-compose-crypto-policy) (eq compose-type 'new)) ;; new messages (and (eq compose-type 'forward) ;; forwarded (memq 'encrypt-forwarded-messages mu4e-compose-crypto-policy)) (and (eq compose-type 'edit) ;; edit (memq 'encrypt-edited-messages mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) ;; all replies (memq 'encrypt-all-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) (not encrypted-p) ;; plain replies (memq 'encrypt-plain-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) encrypted-p ;; encrypted replies (memq 'encrypt-encrypted-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) signed-p ;; signed replies (memq 'encrypt-signed-replies mu4e-compose-crypto-policy)))) (sign (or (memq 'sign-all-messages mu4e-compose-crypto-policy) (and (eq compose-type 'new) ;; new messages (memq 'sign-new-messages mu4e-compose-crypto-policy)) (and (eq compose-type 'forward) ;; forwarded messages (memq 'sign-forwarded-messages mu4e-compose-crypto-policy)) (and (eq compose-type 'edit) ;; edited messages (memq 'sign-edited-messages mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) ;; all replies (memq 'sign-all-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) (not encrypted-p) ;; plain replies (memq 'sign-plain-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) encrypted-p ;; encrypted replies (memq 'sign-encrypted-replies mu4e-compose-crypto-policy)) (and (eq compose-type 'reply) signed-p ;; signed replies (memq 'sign-signed-replies mu4e-compose-crypto-policy))))) (cond ((and sign encrypt) (mml-secure-message-sign-encrypt)) (sign (mml-secure-message-sign)) (encrypt (mml-secure-message-encrypt))))) (defcustom mu4e-sent-messages-behavior 'sent "Determines what mu4e does with sent messages. This is one of the symbols: * `sent' move the sent message to the Sent-folder (`mu4e-sent-folder') * `trash' move the sent message to the Trash-folder (`mu4e-trash-folder') * `delete' delete the sent message. Note, when using GMail/IMAP, you should set this to either `trash' or `delete', since GMail already takes care of keeping copies in the sent folder. Alternatively, `mu4e-sent-messages-behavior' can be a function which takes no arguments, and which should return one of the mentioned symbols, for example: (setq mu4e-sent-messages-behavior (lambda () (if (string= (message-sendmail-envelope-from) \"foo@example.com\") \\='delete \\='sent))) The various `message-' functions from `message-mode' are available for querying the message information." :type '(choice (const :tag "move message to mu4e-sent-folder" sent) (const :tag "move message to mu4e-trash-folder" trash) (const :tag "delete message" delete)) :group 'mu4e-compose) (defcustom mu4e-compose-context-policy 'ask "Policy for determining the context when composing a new message. If the value is `always-ask', ask the user unconditionally. In all other cases, if any context matches (using its match function), this context is used. Otherwise, if none of the contexts match, we have the following choices: - `pick-first': pick the first of the contexts available (ie. the default) - `ask': ask the user - `ask-if-none': ask if there is no context yet, otherwise leave it as it is - nil: return nil; leaves the current context as is. Also see `mu4e-context-policy'." :type '(choice (const :tag "Always ask what context to use" always-ask) (const :tag "Ask if none of the contexts match" ask) (const :tag "Ask when there's no context yet" ask-if-none) (const :tag "Pick the first context if none match" pick-first) (const :tag "Don't change the context when none match" nil)) :safe 'symbolp :group 'mu4e-compose) (defcustom mu4e-compose-jump-to-a-reasonable-place t "Put point at some reasonable place in the compose buffer? After the compose-buffer has been setup, mu4e can jump to some reasonable place as per `mu4e--jump-to-a-reasonable-place'. If you don't consider that reasonable, set to nil." :type 'boolean :safe 'booleanp :group 'mu4e-compose) (defcustom mu4e-compose-reply-include-mime-types '("text/x-patch") "MIME types from the parent message to include when replying. When replying to a message, MIME parts with content-types in this list are automatically included in the the reply message. This is useful for carrying over patches or other structured text parts. Set to nil to disable." :type '(repeat string) :group 'mu4e-compose) ;; ;; display the ready-to-go display buffer in the desired way. ;; (defun mu4e--display-draft-buffer (cbuf) "Display the message composition buffer CBUF. Display is influenced by `mu4e-compose-switch'." (let ((func (pcase mu4e-compose-switch ('nil #'switch-to-buffer) ('window #'switch-to-buffer-other-window) ((or 'frame 't) #'switch-to-buffer-other-frame) ('display-buffer #'display-buffer) (_ (mu4e-error "Invalid mu4e-compose-switch"))))) (funcall func cbuf))) (defvar mu4e-user-agent-string (format "mu4e %s; emacs %s" mu4e-mu-version emacs-version) "The User-Agent string for mu4e, or nil.") ;;; Runtime variables; useful for user-hooks etc. ;; mu4e-compose-parent-message & mu4e-compose-type are buffer-local and ;; permanent-local so they'll survive the mode change to mu4e-compose-mode and ;; we can use them in the corresponding mode-hook. (defvar-local mu4e-compose-parent-message nil "The parent message plist. This is the message being replied to, forwarded or edited; used in `mu4e-compose-pre-hook'. For new (non-reply, forward etc.) messages, it is nil.") (put 'mu4e-compose-parent-message 'permanent-local t) (defvar-local mu4e-compose-type nil "The compose-type for the current message.") (put 'mu4e-compose-type 'permanent-local t) ;;; Filenames (defun mu4e--draft-basename() "Construct a randomized filename for a message with flags FLAGSTR. It looks something like