From 08cb28da8c4893920251a922a7d604f0c9fe3459 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Sun, 15 Jan 2023 12:19:23 +0200 Subject: [PATCH] mu4e: implement optional desktop notifications --- mu4e/meson.build | 1 + mu4e/mu4e-notification.el | 90 +++++++++++++++++++++++++++++++++++++++ mu4e/mu4e.el | 30 +++++++------ mu4e/mu4e.texi | 69 ++++++++++++++++++++---------- 4 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 mu4e/mu4e-notification.el diff --git a/mu4e/meson.build b/mu4e/meson.build index ce92438d..6b8e7556 100644 --- a/mu4e/meson.build +++ b/mu4e/meson.build @@ -46,6 +46,7 @@ mu4e_srcs=[ 'mu4e-mark.el', 'mu4e-message.el', 'mu4e-modeline.el', + 'mu4e-notification.el', 'mu4e-obsolete.el', 'mu4e-org.el', 'mu4e-query-items.el', diff --git a/mu4e/mu4e-notification.el b/mu4e/mu4e-notification.el new file mode 100644 index 00000000..6295445e --- /dev/null +++ b/mu4e/mu4e-notification.el @@ -0,0 +1,90 @@ +;;; mu4e-notification.el --- -*- coding: utf-8; lexical-binding: t -*- +;; +;; Copyright (C) 1996-2023 Dirk-Jan C. Binnema + +;;; Commentary: +;;; Generic support for showing new-mail notifications. + +;;; Code: + +(require 'mu4e-query-items) +(require 'mu4e-bookmarks) + +;; for emacs' built-in desktop notifications to work, we need +;; dbus +(when (featurep 'dbus) + (require 'notifications)) + +(defcustom mu4e-notification-filter #'mu4e--default-notification-filter + "Function for determining if a notification is to be emitted. + +If this is the case, the function should return non-nil. + +The function must accept an optional single parameter, unused for +now." + :type 'function + :group 'mu4e-notification) + +(defcustom mu4e-notification-function + #'mu4e--default-notification-function + "Function to emit a notification. + +The function is invoked when we need to emit a new-mail +notification in some system-specific way. The function is invoked +when the query-items have been updated and +`mu4e-notification-filter' returns t. + +The function must accept an optional single parameter, unused for +now." + :type 'function + :group 'mu4e-notification) + +(defun mu4e--default-notification-filter (&optional _) + "Return t if a notification should be shown. + +This default implementation does so when there are more unread +messages since baseline for the favorite bookmark." + (when-let ((fav (mu4e-bookmark-favorite))) + (> (or (plist-get fav :delta-unread) 0) 0))) + +(defvar mu4e--notification-id nil + "The last notification id, so we can replace it.") + +(defun mu4e--default-notification-function (&optional _) + "Default function for handling notifications. +The default implementation uses emacs' built-in dbus-notification +support." + (when-let ((fav (mu4e-bookmark-favorite))) + (let* ((title "Mu4e found new mail") + (delta-unread (or (plist-get fav :delta-unread) 0)) + (body (format "There %s %d new message%s for your favorite bookmark" + (if (= delta-unread 1) "is" "are") + delta-unread + (if (= delta-unread 1) "" "s")))) + (cond + ((fboundp 'notifications-notify) + ;; notifactions available + (setq mu4e--notification-id + (notifications-notify + :title title + :body body + :app-name "mu4e@emacs" + :replaces-id mu4e--notification-id + ;; a custom mu4e icon would be nice... + ;; :app-icon (ignore-errors + ;; (image-search-load-path + ;; "gnus/gnus.png")) + :actions '("Show" "Favorite bookmark") + :on-action (lambda (_ _) (mu4e-jump-to-favorite))))) + ;; ... TBI: other notifications ... + (t ;; last resort + (mu4e-message "%s: %s" title body)))))) + +(defun mu4e--notification () + "Function called when the query items have been updated." + (when (and (funcall mu4e-notification-filter) + (functionp mu4e-notification-function)) + (funcall mu4e-notification-function))) + +(provide 'mu4e-notification) +;;; mu4e-notification.el ends here diff --git a/mu4e/mu4e.el b/mu4e/mu4e.el index 7dce1c26..6ae401a1 100644 --- a/mu4e/mu4e.el +++ b/mu4e/mu4e.el @@ -39,6 +39,7 @@ (require 'mu4e-bookmarks) (require 'mu4e-update) (require 'mu4e-main) +(require 'mu4e-notification) (require 'mu4e-server) ;; communication with backend @@ -49,7 +50,12 @@ :group 'mu4e) (defcustom mu4e-modeline-support t - "Support for shoiwing information in the modeline." + "Support for showing information in the modeline." + :type 'boolean + :group 'mu4e) + +(defcustom mu4e-notification-support nil + "Support for new-message notifications." :type 'boolean :group 'mu4e) @@ -82,8 +88,8 @@ is non-nil." (interactive "P") ;; start mu4e, then show the main view (mu4e--init-handlers) - (mu4e--start (unless background #'mu4e--main-view)) - (mu4e--query-items-refresh)) + (mu4e--query-items-refresh) + (mu4e--start (unless background #'mu4e--main-view))) (defun mu4e-quit() "Quit the mu4e session." @@ -134,13 +140,6 @@ Invoke FUNC if non-nil." (lambda () (mu4e-update-mail-and-index mu4e-index-update-in-background))))))) - ;; ;; 2. update the main view, if any - ;; (when-let ((mainbuf (get-buffer mu4e-main-buffer-name))) - ;; (when (buffer-live-p mainbuf) - ;; (mu4e--main-redraw-buffer))) - ;; ;; 3. modeline. - ;; (mu4e--modeline-update) - (defun mu4e--start (&optional func) "Start mu4e. If `mu4e-contexts' have been defined, but we don't have a context @@ -153,12 +152,16 @@ Otherwise, check requirements, then start mu4e. When successful, invoke (mu4e--context-autoswitch nil mu4e-context-policy)) (setq mu4e-pong-func (lambda (info) (mu4e--pong-handler info func))) - (mu4e--modeline-register #'mu4e--bookmarks-modeline-item 'global) + ;; modeline support (when mu4e-modeline-support + (mu4e--modeline-register #'mu4e--bookmarks-modeline-item 'global) (mu4e-modeline-mode) (add-hook 'mu4e-query-items-updated-hook #'mu4e--modeline-update)) (mu4e-modeline-mode (if mu4e-modeline-support 1 -1)) + (when mu4e-notification-support + (add-hook 'mu4e-query-items-updated-hook + #'mu4e--notification)) (mu4e--server-ping) ;; maybe request the list of contacts, automatically refreshed after ;; reindexing @@ -172,6 +175,8 @@ Otherwise, check requirements, then start mu4e. When successful, invoke (mu4e-clear-caches) (remove-hook 'mu4e-query-items-updated-hook #'mu4e--modeline-update) + (remove-hook 'mu4e-query-items-updated-hook + #'mu4e--notification) (mu4e-kill-update-mail) (mu4e-modeline-mode -1) (mu4e--server-kill) @@ -271,6 +276,7 @@ chance." mu4e-maildir-list nil mu4e--contacts-set nil mu4e--contacts-tstamp "0")) -;;; _ + +;;; (provide 'mu4e) ;;; mu4e.el ends here diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index dc6fa922..9ad4be87 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -96,6 +96,7 @@ with answers to frequently asked questions, @ref{FAQ}. * Dynamic folders:: Folders that change based on circumstances * Actions:: Defining and using custom actions * Extending mu4e:: Writing code for @t{mu4e} +* Integration:: Integrating @t{mu4e} with Emacs facilities Appendices * Other tools:: mu4e and the rest of the world @@ -2479,7 +2480,7 @@ contexts very often, and e.g. manually changes contexts with @kbd{M-x mu4e-context-switch}. @end itemize -You can easily switch contexts manually using the @kbd{;} key from +You can easily switch contexts manually using the @kbd{;} key from the main screen, or by pressing @kbd{C-c ;} when drafting a message using the editor view. @@ -3059,23 +3060,46 @@ see @code{mu4e-toggle-logging}. @code{user-error} and @code{error} functions. @end itemize -@node Integrating with Emacs -@chapter Integrating with Emacs +@node Integration +@chapter Integrating @t{mu4e} with Emacs facilities In this chapter, we discuss how you can integrate @t{mu4e} with Emacs in various ways. Here we focus on Emacs built-ins; for dealing with external tools, @xref{Other tools}. @menu -* Modeline::Showing mu4e's status in the modeline * Default email client::Making mu4e the default emacs e-mail program -* Using bookmarks::Using Emacs' bookmark system +* Modeline::Showing mu4e's status in the modeline +* Desktop notifications::Get desktop notifications for new mail +* Emacs bookmarks::Using Emacs' bookmark system * Org-mode links::Adding mu4e to your organized life * iCalendar::Enabling iCalendar invite processing * Speedbar::A special frame with your folders * Dired:: Attaching files using @t{dired} @end menu + +@node Default email client +@section Default email client + +Emacs allows you to select an e-mail program as the default program it uses when +you press @key{C-x m} (@code{compose-mail}), call @code{report-emacs-bug} and so +on. If you want to use @t{mu4e} for this, you can do so by adding the following +to your configuration: + +@lisp +(setq mail-user-agent 'mu4e-user-agent) +@end lisp + +Similarly, to specify @t{mu4e} as your preferred method for reading +mail, customize the variable @code{read-mail-command}. + +@lisp +(set-variable 'read-mail-command 'mu4e) +@end lisp + +(@pxref{Top,,Emacs,Sending Mail, Mail Methods}) + @node Modeline @section Modeline @cindex modeline @@ -3130,29 +3154,28 @@ Due to the way queries work, the modeline is @emph{not} immediately updated when you read messages; but going back to the main view (with @kbd{M-x mu4e} restores things. -@node Default email client -@section Default email client +@node Desktop notifications +@section Desktop notifications -Emacs allows you to select an e-mail program as the default program it uses when -you press @key{C-x m} (@code{compose-mail}), call @code{report-emacs-bug} and so -on. If you want to use @t{mu4e} for this, you can do so by adding the following -to your configuration: +Depending on your desktop environment, it is possible to get notification when +there is new mail. -@lisp -(setq mail-user-agent 'mu4e-user-agent) -@end lisp +The default implementation (which you can override) depends on the same system +used for the @xref{Bookmarks and Maildirs} in the main view and the +@xref{Modeline}, and thus gives updates when there new messages compared to some +``baseline'', as discussed earlier. -Similarly, to specify @t{mu4e} as your preferred method for reading -mail, customize the variable @code{read-mail-command}. +For now, notifications are implemented for desktop environments that support +DBus-based notifications, as per Emacs' notification sub-system +@xref{Desktop Notifications,,,elisp,GNU Emacs Lisp Reference Manual}. -@lisp -(set-variable 'read-mail-command 'mu4e) -@end lisp +You can enable mu4e's desktop notifications (provided that you are on a +supported system) by setting @code{mu4e-notification-support} to @t{t}. If you +want tweak the details, have a look at @code{mu4e-notification-filter} and +@code{mu4e-notification-function}. -(@pxref{Top,,Emacs,Sending Mail, Mail Methods}) - -@node Using bookmarks -@section Using bookmarks +@node Emacs bookmarks +@section Emacs bookmarks Note, emacs bookmarks are not to be confused with mu4e's bookmarks; the former are a generic linking system, while the latter are remember stored queries.