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.
This commit is contained in:
5
NEWS.org
5
NEWS.org
@ -312,6 +312,11 @@
|
|||||||
- 1.12.14: add ~mu4e-quit-hook~, a hook is just before quitting mu4e. See its
|
- 1.12.14: add ~mu4e-quit-hook~, a hook is just before quitting mu4e. See its
|
||||||
docstring for details.
|
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
|
*** scm
|
||||||
|
|
||||||
- 1.12.12: add new guile/scheme binding in ~scm/~. These are to replace the
|
- 1.12.12: add new guile/scheme binding in ~scm/~. These are to replace the
|
||||||
|
|||||||
@ -48,7 +48,6 @@ mu4e_srcs=[
|
|||||||
'mu4e-contrib.el',
|
'mu4e-contrib.el',
|
||||||
'mu4e-draft.el',
|
'mu4e-draft.el',
|
||||||
'mu4e-folders.el',
|
'mu4e-folders.el',
|
||||||
'mu4e.el',
|
|
||||||
'mu4e-headers.el',
|
'mu4e-headers.el',
|
||||||
'mu4e-helpers.el',
|
'mu4e-helpers.el',
|
||||||
'mu4e-icalendar.el',
|
'mu4e-icalendar.el',
|
||||||
@ -70,7 +69,8 @@ mu4e_srcs=[
|
|||||||
'mu4e-update.el',
|
'mu4e-update.el',
|
||||||
'mu4e-vars.el',
|
'mu4e-vars.el',
|
||||||
'mu4e-view.el',
|
'mu4e-view.el',
|
||||||
'mu4e-window.el'
|
'mu4e-window.el',
|
||||||
|
'mu4e.el'
|
||||||
]
|
]
|
||||||
|
|
||||||
# emacs 28 is guaranteed to have transient
|
# emacs 28 is guaranteed to have transient
|
||||||
@ -80,6 +80,13 @@ if emacs28.found()
|
|||||||
mu4e_srcs += 'mu4e-transient.el'
|
mu4e_srcs += 'mu4e-transient.el'
|
||||||
endif
|
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
|
# note, we cannot compile mu4e-config.el without incurring
|
||||||
# WARNING: Source item
|
# WARNING: Source item
|
||||||
# '[...]/build/mu4e/mu4e-meta.el' cannot be converted to File object, because
|
# '[...]/build/mu4e/mu4e-meta.el' cannot be converted to File object, because
|
||||||
|
|||||||
240
mu4e/mu4e-dbus.el
Normal file
240
mu4e/mu4e-dbus.el
Normal file
@ -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
|
||||||
@ -49,6 +49,8 @@
|
|||||||
(require 'mu4e-speedbar)) ;; support for speedbar
|
(require 'mu4e-speedbar)) ;; support for speedbar
|
||||||
(when mu4e-org-support
|
(when mu4e-org-support
|
||||||
(require 'mu4e-org)) ;; support for org-mode links
|
(require 'mu4e-org)) ;; support for org-mode links
|
||||||
|
(when (featurep 'dbusbind)
|
||||||
|
(require 'mu4e-dbus))
|
||||||
|
|
||||||
(defcustom mu4e-quit-hook nil
|
(defcustom mu4e-quit-hook nil
|
||||||
"Hook run just before quitting mu4e.
|
"Hook run just before quitting mu4e.
|
||||||
|
|||||||
@ -3494,6 +3494,7 @@ see @ref{Other tools}.
|
|||||||
* Modeline::Showing mu4e's status in the modeline
|
* Modeline::Showing mu4e's status in the modeline
|
||||||
* Transient::Transient menus for mu4e
|
* Transient::Transient menus for mu4e
|
||||||
* Desktop notifications::Get desktop notifications for new mail
|
* Desktop notifications::Get desktop notifications for new mail
|
||||||
|
* DBus service::Accessing Mu4e through DBus
|
||||||
* Emacs bookmarks::Using Emacs' bookmark system
|
* Emacs bookmarks::Using Emacs' bookmark system
|
||||||
* Eldoc::Information about the current header in the echo area
|
* Eldoc::Information about the current header in the echo area
|
||||||
* Org-mode::Adding mu4e to your organized life
|
* 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
|
want to tweak the details, have a look at @code{mu4e-notification-filter} and
|
||||||
@code{mu4e-notification-function}.
|
@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
|
@node Emacs bookmarks
|
||||||
@section Emacs bookmarks
|
@section Emacs bookmarks
|
||||||
@cindex Emacs bookmarks
|
@cindex Emacs bookmarks
|
||||||
|
|||||||
Reference in New Issue
Block a user