diff options
| author | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-12-02 22:34:15 +0200 |
|---|---|---|
| committer | Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> | 2025-12-08 19:10:19 +0200 |
| commit | a8e1bcbb4661641c5c7560df4263eaa3c2480c96 (patch) | |
| tree | 040ec80b022b7bf035014ea2d9a565f441c89405 | |
| parent | 46f91ab7b7641caac992a41264ac41a76c001c87 (diff) | |
mu4e: implement mu4e-dbus-mode
For emacsen that support DBus, implement mu4e-dbus-mode, an experimental mode to
make some mu4e runtime information available outside emacs.
| -rw-r--r-- | NEWS.org | 5 | ||||
| -rw-r--r-- | mu4e/meson.build | 11 | ||||
| -rw-r--r-- | mu4e/mu4e-dbus.el | 240 | ||||
| -rw-r--r-- | mu4e/mu4e.el | 2 | ||||
| -rw-r--r-- | mu4e/mu4e.texi | 69 |
5 files changed, 325 insertions, 2 deletions
@@ -312,6 +312,11 @@ - 1.12.14: add ~mu4e-quit-hook~, a hook is just before quitting mu4e. See its docstring for details. + - 1.12.14: add ~mu4e-dbus-mode~, an experimental minor mode which, if enabled on an Emacs + with DBus-support, exposes some of Mu4e's state (such as the the + read/unread counts of bookmarked queries) on the DBus session bus. + See [[info:mu4e#DBus + service][DBus service]] in the mu4e manual for further details. *** scm - 1.12.12: add new guile/scheme binding in ~scm/~. These are to replace the diff --git a/mu4e/meson.build b/mu4e/meson.build index f117f79..5f0da4d 100644 --- a/mu4e/meson.build +++ b/mu4e/meson.build @@ -48,7 +48,6 @@ mu4e_srcs=[ 'mu4e-contrib.el', 'mu4e-draft.el', 'mu4e-folders.el', - 'mu4e.el', 'mu4e-headers.el', 'mu4e-helpers.el', 'mu4e-icalendar.el', @@ -70,7 +69,8 @@ mu4e_srcs=[ 'mu4e-update.el', 'mu4e-vars.el', 'mu4e-view.el', - 'mu4e-window.el' + 'mu4e-window.el', + 'mu4e.el' ] # emacs 28 is guaranteed to have transient @@ -80,6 +80,13 @@ if emacs28.found() mu4e_srcs += 'mu4e-transient.el' endif +# do have we have dbus support? +emacs_dbus = run_command(emacs, '--batch', '--eval', '(kill-emacs (if (featurep \'dbusbind) 0 1))', + check: false) +if emacs_dbus.returncode() == 0 + mu4e_srcs += 'mu4e-dbus.el' +endif + # note, we cannot compile mu4e-config.el without incurring # WARNING: Source item # '[...]/build/mu4e/mu4e-meta.el' cannot be converted to File object, because diff --git a/mu4e/mu4e-dbus.el b/mu4e/mu4e-dbus.el new file mode 100644 index 0000000..ba06413 --- /dev/null +++ b/mu4e/mu4e-dbus.el @@ -0,0 +1,240 @@ +;;; mu4e-dbus.el --- DBus support -*- lexical-binding: t-*- + +;; Copyright (C) 2025 Dirk-Jan C. Binnema + +;; Author: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> +;; Maintainer: Dirk-Jan C. Binnema <djcb@djcbsoftware.nl> + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; 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 <http://www.gnu.org/licenses/>. + +;;; Commentary: +;;; Generic support for showing new-mail notifications. + +;;; Code: + +(eval-when-compile (require 'dbus nil 'noerror)) +(require 'dbus nil 'noerror) + +(require 'mu4e-helpers) +(require 'mu4e-query-items) +(require 'mu4e-context) + +(defconst mu4e-dbus-service "nl.djcbsoftware.Mu4e" + "Mu4e's DBus service name.") + +(defconst mu4e-dbus-interface "nl.djcbsoftware.Mu4e" + "Mu4e's DBus interface.") + +(defconst mu4e-dbus-object-path "/nl/djcbsoftware/Mu4e" + "Mu4e's DBus object path.") +;; We could use different object paths for allowing multiple mu4e's. But let's +;; not add that complication for now. + +(defconst mu4e-dbus-introspection-xml + "<node> + <interface name='org.freedesktop.DBus.Introspectable'> + <method name='Introspect'> + <arg name='xml_data' type='s' direction='out'/> + </method> + </interface> + <interface name='org.freedesktop.DBus.Properties'> + <method name='Get'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='s' name='property_name' direction='in'/> + <arg type='v' name='value' direction='out'/> + </method> + <method name='GetAll'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='a{sv}' name='properties' direction='out'/> + </method> + <method name='Set'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='s' name='property_name' direction='in'/> + <arg type='v' name='value' direction='in'/> + </method> + <signal name='PropertiesChanged'> + <arg type='s' name='interface_name'/> + <arg type='a{sv}' name='changed_properties'/> + <arg type='as' name='invalidated_properties'/> + </signal> + </interface> + <interface name='nl.djcbsoftware.Mu4e'> + <property name='Version' type='s' access='read'/> + <property name='DatabasePath' type='s' access='read'/> + <property name='RootMaildir' type='s' access='read'/> + <property name='Context' type='s' access='readwrite'/> + <property name='QueryInfo' type='aa{sv}' access='readwrite'/> + </interface> +</node>" + "The XML blob used for DBus introspection. + +Apart from the properties boilerplate, the +\"nl.djcbsoftware.Mu4e\" interface supports the following: + +1) properties: + - Version (string): the mu/mu4e version + - Context (string): the current mu4e context, if any + - QueryInfo (array-of-vardict): + information about bookmarks / queries, i.e., a + subset of the `mu4e-query-items' information. + + Each item describes a bookmark/maildir item with + entries: + - name (string) -> name of the item + - query (string) -> query for this item + - count (number) -> number of matching messages + - unread (number) -> the number of unread messages + - favorite (bool) -> this is the favorite entry + (optional, at most one entry has this).") + +;; Introspection... although not strictly required, it is very useful for +;; clients (including tools like d-spy) to find out the methods we support. We +;; need to implement introspection methods on each hierarchical level of the +;; object path... a lot of boilerplate +;; +;; I think Emacs could technically generate much of this, but it doesn't seem to +;; do so now.... + +(defun mu4e--dbus-introspect-parents () + "Set up introspection for the parent object paths." + (seq-do + (lambda (path-child) + (let ((path (car path-child)) (child (cdr path-child))) + (dbus-register-method + :session mu4e-dbus-service + path + dbus-interface-introspectable + "Introspect" + `(lambda () + ,(format "<node><node name='%s'/></node>" child))))) + '(("/" . "nl") + ("/nl" . "djcbsoftware") + ("/nl/djcbsoftware" . "Mu4e")))) + +(defun mu4e--dbus-introspect-nl-djcbsoftware-mu4e () + "Mu4e introspection for /nl/djcbsoftware/Mu4e." + (dbus-register-method + :session + mu4e-dbus-service + mu4e-dbus-object-path + dbus-interface-introspectable + "Introspect" + (lambda () mu4e-dbus-introspection-xml))) + +(defun mu4e--dbus-register-property (name access value) + "Register mu4e DBus property with NAME to VALUE. + +ACCESS is a symbol, either `:read', `:write' or `:readwrite'. + +There are read-only, i.e. only writable from here. Thus, +setting a property means (re)registering it." + (dbus-register-property + :session + mu4e-dbus-service + mu4e-dbus-object-path + mu4e-dbus-interface + name access value)) + +(defun mu4e--dbus-set-property (name value) + "Update mu4e DBus property with NAME to VALUE." + (dbus-set-property + :session + mu4e-dbus-service + mu4e-dbus-object-path + mu4e-dbus-interface + name value)) + +;; Convert to D-Bus format - list of dict entries +(defun mu4e--dbus-query-info () + "Return an array of dbus vardicts with query-items information. + +This filters / maps the information from `mu4e-query-items'." + ;; D-Bus signature: aa{sv} (array of dict with string keys and variant values) + ;; Return an array of dbus vardicts with query-items information. + ;; This filters / maps the information from `mu4e-query-items'." + ;; D-Bus signature: aa{sv} (array of dict with string keys and variant values) + (cons ':array + (mapcar + (lambda (plist) + (let ((dict)) + (while plist + (let* ((key (car plist)) (val (cadr plist))) + (when (memq key '(:name :query :count :unread :favorite)) + (push (list :dict-entry (substring (symbol-name key) 1) ; eat ':' + (list :variant val)) dict)) + (setq plist (cddr plist)))) + (cons ':array dict))) + (mu4e-query-items)))) + +(defun mu4e--dbus-update-query-info () + "(Re-)register the query-info property." + (mu4e--dbus-set-property "QueryInfo" (mu4e--dbus-query-info))) + +(defun mu4e--dbus-update-context () + "(Re-)register the current mu4e context." + (let ((ctx (mu4e-context-current))) + (mu4e--dbus-set-property + "Context" (if ctx (mu4e-context-name ctx) "")))) + +(defun mu4e--dbus-init () + "Initialize mu4e's DBus interface." + (dbus-register-service + :session mu4e-dbus-service + :allow-replacement :replace-existing) + + ;; setup introspection + (mu4e--dbus-introspect-parents) + (mu4e--dbus-introspect-nl-djcbsoftware-mu4e) + + ;; Register properties + (mu4e--dbus-register-property "Version" :read mu4e-mu-version) + (mu4e--dbus-register-property "DatabasePath" :read + (plist-get (mu4e-server-properties) :database-path)) + (mu4e--dbus-register-property "RootMaildir" :read + (plist-get (mu4e-server-properties) :root-maildir)) + (mu4e--dbus-register-property "MessageCounbt" :readwrite + (plist-get (mu4e-server-properties) :doccount)) + (mu4e--dbus-register-property "Context" :readwrite mu4e-mu-version) + (mu4e--dbus-register-property "QueryInfo" :readwrite (mu4e--dbus-query-info)) + + ;; auto-update context + (mu4e--dbus-update-context) + (add-hook 'mu4e-context-changed-hook #'mu4e--dbus-update-context) + + ;; auto-update query info, and update when it changes. + (mu4e--dbus-update-query-info) + (add-hook 'mu4e-query-items-updated-hook #'mu4e--dbus-update-query-info)) + +(defun mu4e--dbus-uninit () + "Un-initialize mu4e's DBus interface." + (ignore-errors (dbus-unregister-service :session mu4e-dbus-service)) + (remove-hook 'mu4e-context-changed-hook #'mu4e--dbus-update-context) + (remove-hook 'mu4e-query-items-updated-hook #'mu4e--dbus-update-query-info)) + +;;;###autoload +(define-minor-mode mu4e-dbus-mode + "Minor mode for enabling and disable the mu4e DBus service." + :global t + :group 'mu4e + :lighter nil + (unless (featurep 'dbus) + (mu4e-error "DBus support is required but not found")) + (if mu4e-dbus-mode + (mu4e--dbus-init) + (mu4e--dbus-uninit))) + +(provide 'mu4e-dbus) +;;; mu4e-dbus.el ends here diff --git a/mu4e/mu4e.el b/mu4e/mu4e.el index 98562b7..1604810 100644 --- a/mu4e/mu4e.el +++ b/mu4e/mu4e.el @@ -49,6 +49,8 @@ (require 'mu4e-speedbar)) ;; support for speedbar (when mu4e-org-support (require 'mu4e-org)) ;; support for org-mode links +(when (featurep 'dbusbind) + (require 'mu4e-dbus)) (defcustom mu4e-quit-hook nil "Hook run just before quitting mu4e. diff --git a/mu4e/mu4e.texi b/mu4e/mu4e.texi index d6bd8d5..f8d11a5 100644 --- a/mu4e/mu4e.texi +++ b/mu4e/mu4e.texi @@ -3494,6 +3494,7 @@ see @ref{Other tools}. * Modeline::Showing mu4e's status in the modeline * Transient::Transient menus for mu4e * Desktop notifications::Get desktop notifications for new mail +* DBus service::Accessing Mu4e through DBus * Emacs bookmarks::Using Emacs' bookmark system * Eldoc::Information about the current header in the echo area * Org-mode::Adding mu4e to your organized life @@ -3625,6 +3626,74 @@ supported system) by setting @code{mu4e-notification-support} to @t{t}. If you want to tweak the details, have a look at @code{mu4e-notification-filter} and @code{mu4e-notification-function}. +@node DBus service +@section DBus service +@cindex DBus + +DBus is a message-bus system for GNU/Linux and some other systems, which allows +for communications between programs using a binary protocol; see @ref{(dbus) +Top} for further details. + +@code{mu4e} uses Emacs' DBus-support to implements a service for making some of +its information visible to external programs. This information includes the +current context (@code{mu4e-context-name}) and the read/unread counts for +various bookmarked queries (@code{mu4e-query-items})- the same information that +is used for the new mail notifications and the read/unread counts, +@pxref{Bookmarks and Maildirs}. + +An external program could use this information display such status information. + +@subsection @code{mu4e-dbus-mode} +@cindex mu4e-dbus-mode + +Enabling and disabling the service is through a minor-mode +@code{mu4e-dbus-mode}. @code{M-x mu4e-dbus-mode}. + +This starts a DBus service with the name @t{nl.djcbsoftware.nl} on the +session-bus, and at object-path @t{nl.djcbsoftware.Mu4e}, exposes the interface +@t{/nl/djcbsoftware/Mu4e}. + +The interface is described as an XML string, in a constant +@code{mu4e-dbus-intrspection-xml}. This currently looks like: + +@verbatim + <node> + <interface name='org.freedesktop.DBus.Introspectable'> + <method name='Introspect'> + <arg name='xml_data' type='s' direction='out'/> + </method> + </interface> + <interface name='org.freedesktop.DBus.Properties'> + <method name='Get'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='s' name='property_name' direction='in'/> + <arg type='v' name='value' direction='out'/> + </method> + <method name='GetAll'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='a{sv}' name='properties' direction='out'/> + </method> + <method name='Set'> + <arg type='s' name='interface_name' direction='in'/> + <arg type='s' name='property_name' direction='in'/> + <arg type='v' name='value' direction='in'/> + </method> + <signal name='PropertiesChanged'> + <arg type='s' name='interface_name'/> + <arg type='a{sv}' name='changed_properties'/> + <arg type='as' name='invalidated_properties'/> + </signal> + </interface> + <interface name='nl.djcbsoftware.Mu4e'> + <property name='Version' type='s' access='read'/> + <property name='DatabasePath' type='s' access='read'/> + <property name='RootMaildir' type='s' access='read'/> + <property name='Context' type='s' access='readwrite'/> + <property name='QueryInfo' type='aa{sv}' access='readwrite'/> + </interface> +</node> +@end verbatim + @node Emacs bookmarks @section Emacs bookmarks @cindex Emacs bookmarks |
