diff --git a/doc/misc/dbus.texi b/doc/misc/dbus.texi index 7fad406520c..946e7666629 100644 --- a/doc/misc/dbus.texi +++ b/doc/misc/dbus.texi @@ -64,6 +64,7 @@ another. An overview of D-Bus can be found at * Alternative Buses:: Alternative buses and environments. * Errors and Events:: Errors and events. * Monitoring Messages:: Monitoring messages. +* Inhibitor Locks:: Inhibit system shutdowns and sleep states. * Index:: Index including concepts, functions, variables. * GNU Free Documentation License:: The license for this documentation. @@ -124,7 +125,7 @@ name could be @samp{org.gnu.Emacs.TextEditor} or @node Inspection -@chapter Inspection of D-Bus services. +@chapter Inspection of D-Bus services @cindex inspection @menu @@ -139,7 +140,7 @@ name could be @samp{org.gnu.Emacs.TextEditor} or @node Version -@section D-Bus version. +@section D-Bus version D-Bus has evolved over the years. New features have been added with new D-Bus versions. There are two variables, which allow the determination @@ -158,7 +159,7 @@ It is also @code{nil}, if it cannot be determined at runtime. @node Bus names -@section Bus names. +@section Bus names There are several basic functions which inspect the buses for registered names. Internally they use the basic interface @@ -267,7 +268,7 @@ at D-Bus @var{bus}, as a string. @node Introspection -@section Knowing the details of D-Bus services. +@section Knowing the details of D-Bus services D-Bus services publish their interfaces. This can be retrieved and analyzed during runtime, in order to understand the used @@ -483,7 +484,7 @@ If @var{object} has no @var{attribute}, the function returns @node Nodes and Interfaces -@section Detecting object paths and interfaces. +@section Detecting object paths and interfaces The first elements, to be introspected for a D-Bus object, are further object paths and interfaces. @@ -593,7 +594,7 @@ data from a running system: @node Methods and Signal -@section Applying the functionality. +@section Applying the functionality Methods and signals are the communication means to D-Bus. The following functions return their specifications. @@ -673,7 +674,7 @@ Example: @node Properties and Annotations -@section What else to know about interfaces. +@section What else to know about interfaces Interfaces can have properties. These can be exposed via the @samp{org.freedesktop.DBus.Properties} interface@footnote{See @@ -894,7 +895,7 @@ An attribute value can be retrieved by @node Arguments and Signatures -@section The final details. +@section The final details Methods and signals have arguments. They are described in the @code{arg} XML elements. @@ -962,7 +963,7 @@ non-@code{nil}, @var{direction} must be @samp{out}. Example: @node Type Conversion -@chapter Mapping Lisp types and D-Bus types. +@chapter Mapping Lisp types and D-Bus types @cindex type conversion D-Bus method calls and signals accept usually several arguments as @@ -975,7 +976,7 @@ applied Lisp object @expansion{} D-Bus type for input parameters, and D-Bus type @expansion{} Lisp object for output parameters. -@section Input parameters. +@section Input parameters Input parameters for D-Bus methods and signals occur as arguments of a Lisp function call. The following mapping to D-Bus types is @@ -1116,7 +1117,7 @@ lower-case hex digits. As a special case, "" is escaped to @end defun -@section Output parameters. +@section Output parameters Output parameters of D-Bus methods and signals are mapped to Lisp objects. @@ -1199,7 +1200,7 @@ that string: @node Synchronous Methods -@chapter Calling methods in a blocking way. +@chapter Calling methods in a blocking way @cindex method calls, synchronous @cindex synchronous method calls @@ -1319,7 +1320,7 @@ emulate the @code{lshal} command on GNU/Linux systems: @node Asynchronous Methods -@chapter Calling methods non-blocking. +@chapter Calling methods non-blocking @cindex method calls, asynchronous @cindex asynchronous method calls @@ -1371,7 +1372,7 @@ message arrives, and @var{handler} is called. Example: @node Register Objects -@chapter Offering own services. +@chapter Offering own services @cindex method calls, returning @cindex returning method calls @@ -1722,7 +1723,7 @@ to the service from D-Bus. @node Signals -@chapter Sending and receiving signals. +@chapter Sending and receiving signals @cindex signals Signals are one way messages. They carry input parameters, which are @@ -1859,7 +1860,7 @@ for a dummy signal, and check the result: @node Alternative Buses -@chapter Alternative buses and environments. +@chapter Alternative buses and environments @cindex bus names @cindex UNIX domain socket @cindex TCP/IP socket @@ -1986,7 +1987,7 @@ running. This could be achieved by @node Errors and Events -@chapter Errors and events. +@chapter Errors and events @cindex debugging @cindex errors @cindex events @@ -2145,7 +2146,7 @@ whether a given D-Bus error is related to them. @node Monitoring Messages -@chapter Monitoring messages. +@chapter Monitoring messages @cindex monitoring @defun dbus-register-monitor bus &optional handler &key type sender destination path interface member @@ -2204,6 +2205,111 @@ switches to the monitor buffer. @end deffn +@node Inhibitor Locks +@chapter Inhibit system shutdowns and sleep states + +@uref{https://systemd.io/INHIBITOR_LOCKS/, Systemd} includes a logic to +inhibit system shutdowns and sleep states. It can be controlled by a +D-Bus API@footnote{@uref{https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html}}. +Because this API includes handling of file descriptors, not all +functions can be implemented by simple D-Bus method calls. Therefore, +the following functions are provided. + +@defun dbus-make-inhibitor-lock what why &optional block +This function creates an inhibitor for system shutdowns and sleep states. + +@var{what} is a colon-separated string of lock types: @samp{shutdown}, +@samp{sleep}, @samp{idle}, @samp{handle-power-key}, +@samp{handle-suspend-key}, @samp{handle-hibernate-key}, +@samp{handle-lid-switch}. Example: @samp{shutdown:idle}. + +@c@var{who} is a descriptive string of who is taking the lock. If it is +@c@code{nil}, it defaults to @samp{Emacs}. + +@var{why} is a descriptive string of why the lock is taken. Example: +@samp{Package Update in Progress}. + +The optional @var{block} is the mode of the inhibitor lock, either +@samp{block} (@var{block} is non-@code{nil}), or @samp{delay}. + +Note, that the @code{who} argument of the inhibitor lock object of the +systemd manager is always set to the string @samp{Emacs}. + +It returns a file descriptor or @code{nil}, if the lock cannot be +acquired. If there is already an inhibitor lock for the triple +@code{(WHAT WHY BLOCK)}, this lock is returned. Example: + +@lisp +(dbus-make-inhibitor-lock "sleep" "Test") + +@result{} 25 +@end lisp +@end defun + +@defun dbus-registered-inhibitor-locks +Return registered inhibitor locks, an alist. +This allows to check, whether other packages of the running Emacs +instance have acquired an inhibitor lock as well. + +An entry in this list is a list @code{(@var{fd} @var{what} @var{why} +@var{block})}. The car of the list is the file descriptor retrieved +from a @code{dbus-make-inhibitor-lock} call. The cdr of the list +represents the three arguments @code{dbus-make-inhibitor-lock} was +called with. Example: + +@lisp +(dbus-registered-inhibitor-locks) + +@result{} ((25 "sleep" "Test" nil)) +@end lisp +@end defun + +@defun dbus-close-inhibitor-lock lock +Close inhibitor lock file descriptor. + +@var{lock}, a file descriptor, must be the result of a +@code{dbus-make-inhibitor-lock} call. It returns @code{t} in case of +success, or @code{nil} if it isn't be possible to close the lock, or if +the lock is closed already. Example: + +@lisp +(dbus-close-inhibitor-lock 25) + +@result{} t + +@end lisp +@end defun + +A typical scenario for these functions is to register for the +D-Bus signal @samp{org.freedesktop.login1.Manager.PrepareForSleep}: + +@lisp +(defvar my-inhibitor-lock + (dbus-make-inhibitor-lock "sleep" "Test")) + +(defun my-dbus-PrepareForSleep-handler (start) + (if start ;; The system goes down for sleep + (progn + @dots{} + ;; Release inhibitor lock. + (when (natnump my-inhibitor-lock) + (dbus-close-inhibitor-lock my-inhibitor-lock) + (setq my-inhibitor-lock nil))) + ;; Reacquire inhibitor lock. + (setq my-inhibitor-lock + (dbus-make-inhibitor-lock "sleep" "Test")))) + +(dbus-register-signal + :system "org.freedesktop.login1" "/org/freedesktop/login1/Manager" + "org.freedesktop.login1.Manager" "PrepareForSleep" + #'my-dbus-PrepareForSleep-handler) + +@result{} ((:signal :system "org.freedesktop.login1.Manager" "PrepareForSleep") + ("org.freedesktop.login1" "/org/freedesktop/login1/Manager" + my-dbus-PrepareForSleep-handler)) +@end lisp + + @node Index @unnumbered Index diff --git a/etc/NEWS b/etc/NEWS index 17a6a6c68b0..0b4fcadb620 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -201,10 +201,10 @@ large or inefficient completion tables this can slow down typing. +++ *** New optional value of 'minibuffer-visible-completions'. -If the value of this option is 'up-down', only the and arrow -keys move point between candidates shown in the *Completions* buffer -display, while and arrows move point in the minibuffer -window. +If the value of this option is 'up-down', only the '' and '' +arrow keys move point between candidates shown in the "*Completions*" +buffer display, while '' and '' arrows move point in the +minibuffer window. --- *** 'RET' chooses the completion selected with 'M-/M-'. @@ -513,7 +513,7 @@ Each non-tooltip frame is assigned a unique integer id. This allows you to unambiguously identify frames even if they share the same name or title. When 'undelete-frame-mode' is enabled, each deleted frame's id is stored for resurrection. The function 'frame-id' returns a frame's -id (in C, use the frame struct member id). +id (in C, use the frame struct member 'id'). ** Mode Line @@ -2062,9 +2062,9 @@ you exit the Emacs session or kill the IELM buffer. --- *** New value 'point' for user option 'ielm-dynamic-return'. -When 'ielm-dynamic-return' is set to 'point', typing RET has dynamic +When 'ielm-dynamic-return' is set to 'point', typing 'RET' has dynamic behavior based on whether point is inside an sexp. While point is -inside an sexp typing RET inserts a newline, and otherwise Emacs +inside an sexp typing 'RET' inserts a newline, and otherwise Emacs proceeds with evaluating the expression. This is useful when 'electric-pair-mode', or a similar automatic pairing mode, is enabled. @@ -2889,7 +2889,7 @@ The user option 'package-review-policy' can configure which packages the user should be allowed to review before any processing takes place. The package review can include reading the downloaded source code, presenting a diff between the downloaded code and a previous -installation or displaying a changelog. +installation or displaying a ChangeLog. ** Rcirc @@ -3750,12 +3750,21 @@ without marking it as automatically buffer-local. ** The obsolete face attribute ':reverse-video' has been removed. Use ':inverse-video' instead. +** D-Bus + +++ -** Support interactive D-Bus authorization. +*** Support interactive D-Bus authorization. A new ':authorizable t' parameter has been added to 'dbus-call-method' and 'dbus-call-method-asynchronously' to allow the user to interactively authorize the invoked D-Bus method (for example via polkit). ++++ +*** New D-Bus functions to support systemd inhibitor locks. +The functions 'dbus-make-inhibitor-lock', 'dbus-close-inhibitor-lock' +and 'dbus-registered-inhibitor-locks' implement acquiring and releasing +systemd inhibitor locks. See the Info node "(dbus) Inhibitor Locks" for +details. + ** The customization group 'wp' has been removed. It has been obsolete since Emacs 26.1. Use the group 'text' instead. @@ -3926,15 +3935,17 @@ When the theme is set on PGTK, Android, or MS-Windows systems, variable 'toolkit-theme' as either symbol 'dark' or 'light', but may be extended to encompass other toolkit-specific symbols in the future. +** Progress reporter + +++ -** Progress reporter callbacks. +*** Progress reporter callbacks. 'make-progress-reporter' now accepts optional arguments UPDATE-CALLBACK, called on progress steps, and DONE-CALLBACK, called when the progress reporter is done. See the 'make-progress-reporter' docstring for a full specification of these new optional arguments. +++ -** Progress reporter context. +*** Progress reporter context. 'make-progress-reporter' now accepts the optional argument CONTEXT, which if it is the symbol 'async', inhibits updates in the echo area when it is busy. This is useful, for example, if you want to monitor progress diff --git a/src/dbusbind.c b/src/dbusbind.c index a2936011610..a416e6c918a 100644 --- a/src/dbusbind.c +++ b/src/dbusbind.c @@ -1617,6 +1617,109 @@ usage: (dbus-message-internal &rest REST) */) return result; } +/* Alist of registered inhibitor locks for D-Bus. + An entry in this list is a list (FD WHAT WHY BLOCK). + The car of the list is a file descriptor retrieved from a + 'dbus-make-inhibitor-lock` call. The cdr of the list represents the + three arguments 'dbus-make-inhibitor-lock` was called with. */ +static Lisp_Object xd_registered_inhibitor_locks; + +DEFUN ("dbus-make-inhibitor-lock", Fdbus_make_inhibitor_lock, + Sdbus_make_inhibitor_lock, + 2, 3, 0, + doc: /* Inhibit system shutdowns and sleep states. + +WHAT is a colon-separated string of lock types, i.e. "shutdown", +"sleep", "idle", "handle-power-key", "handle-suspend-key", +"handle-hibernate-key", "handle-lid-switch". Example: "shutdown:idle". + +WHY is a descriptive string of why the lock is taken. Example: "Package +Update in Progress". + +The optional BLOCK is the mode of the inhibitor lock, either "block" +(BLOCK is non-nil), or "delay". + +It returns a file descriptor or nil, if the lock cannot be acquired. If +there is already an inhibitor lock for the triple (WHAT WHY BLOCK), this +lock is returned. + +For details of the arguments, see Info node `(dbus)Inhibitor Locks'. */) + (Lisp_Object what, Lisp_Object why, Lisp_Object block) +{ + CHECK_STRING (what); + CHECK_STRING (why); + if (!NILP (block)) + block = Qt; + Lisp_Object who = build_string ("Emacs"); + Lisp_Object mode = + (NILP (block)) ? build_string ("delay") : build_string ("block"); + + /* Check, whether it is registered already. */ + Lisp_Object triple = list3 (what, why, block); + Lisp_Object registered = Frassoc (triple, xd_registered_inhibitor_locks); + if (!NILP (registered)) + return CAR_SAFE (registered); + + /* Register lock. */ + Lisp_Object lock = + calln (Qdbus_call_method, QCsystem, + build_string ("org.freedesktop.login1"), + build_string ("/org/freedesktop/login1"), + build_string ("org.freedesktop.login1.Manager"), + build_string ("Inhibit"), what, who, why, mode); + + xd_registered_inhibitor_locks = + Fcons (Fcons (lock, triple), xd_registered_inhibitor_locks); + return lock; +} + +DEFUN ("dbus-close-inhibitor-lock", Fdbus_close_inhibitor_lock, + Sdbus_close_inhibitor_lock, + 1, 1, 0, + doc: /* Close inhibitor lock file descriptor. + +LOCK, a file descriptor, must be the result of a `dbus-make-inhibitor-lock' +call. It returns t in case of success, or nil if it isn't be possible +to close the lock, or if the lock is closed already. + +For details, see Info node `(dbus)Inhibitor Locks'. */) + (Lisp_Object lock) +{ + CHECK_FIXNUM (lock); + + /* Check, whether it is registered. */ + Lisp_Object registered = assoc_no_quit (lock, xd_registered_inhibitor_locks); + if (NILP (registered)) + return Qnil; + else + { + xd_registered_inhibitor_locks = + Fdelete (registered, xd_registered_inhibitor_locks); + return (emacs_close (XFIXNAT (lock)) == 0) ? Qt : Qnil; + } +} + +DEFUN ("dbus-registered-inhibitor-locks", Fdbus_registered_inhibitor_locks, + Sdbus_registered_inhibitor_locks, + 0, 0, 0, + doc: /* Return registered inhibitor locks, an alist. +This allows to check, whether other packages of the running Emacs +instance have acquired an inhibitor lock as well. +An entry in this list is a list (FD WHAT WHY BLOCK). +The car of the list is the file descriptor retrieved from a +'dbus-make-inhibitor-lock` call. The cdr of the list represents the +three arguments 'dbus-make-inhibitor-lock` was called with. */) + () +{ + /* We return a copy of xd_registered_inhibitor_locks, in order to + protect it against malicious manipulation. */ + Lisp_Object registered = xd_registered_inhibitor_locks; + Lisp_Object result = Qnil; + for (; !NILP (registered); registered = CDR_SAFE (registered)) + result = Fcons (Fcopy_sequence (CAR_SAFE (registered)), result); + return Fnreverse (result); +} + /* Construct a D-Bus event, and store it into the input event queue. */ static void xd_store_event (Lisp_Object handler, Lisp_Object handler_args, @@ -1869,6 +1972,7 @@ static void syms_of_dbusbind_for_pdumper (void) { xd_registered_buses = Qnil; + xd_registered_inhibitor_locks = Qnil; } void @@ -1876,6 +1980,9 @@ syms_of_dbusbind (void) { defsubr (&Sdbus__init_bus); defsubr (&Sdbus_get_unique_name); + defsubr (&Sdbus_make_inhibitor_lock); + defsubr (&Sdbus_close_inhibitor_lock); + defsubr (&Sdbus_registered_inhibitor_locks); DEFSYM (Qdbus_message_internal, "dbus-message-internal"); defsubr (&Sdbus_message_internal); @@ -1930,6 +2037,7 @@ syms_of_dbusbind (void) /* Miscellaneous Lisp symbols. */ DEFSYM (Qdbus_get_name_owner, "dbus-get-name-owner"); + DEFSYM (Qdbus_call_method, "dbus-call-method"); DEFVAR_LISP ("dbus-compiled-version", Vdbus_compiled_version, @@ -2035,6 +2143,7 @@ be called when the D-Bus reply message arrives. */); /* Initialize internal objects. */ pdumper_do_now_and_after_load (syms_of_dbusbind_for_pdumper); staticpro (&xd_registered_buses); + staticpro (&xd_registered_inhibitor_locks); Fprovide (intern_c_string ("dbusbind"), Qnil); } diff --git a/test/lisp/net/dbus-tests.el b/test/lisp/net/dbus-tests.el index e529e02ed9b..b34ce3381c7 100644 --- a/test/lisp/net/dbus-tests.el +++ b/test/lisp/net/dbus-tests.el @@ -48,6 +48,15 @@ (defconst dbus--test-interface "org.gnu.Emacs.TestDBus.Interface" "Test interface.") +(defconst dbus--test-systemd-service "org.freedesktop.login1" + "Systemd service.") + +(defconst dbus--test-systemd-path "/org/freedesktop/login1" + "Systemd object path.") + +(defconst dbus--test-systemd-manager-interface "org.freedesktop.login1.Manager" + "Systemd Manager interface.") + (defun dbus--test-availability (bus) "Test availability of D-Bus BUS." (should (dbus-list-names bus)) @@ -2295,6 +2304,90 @@ The argument EXPECTED-ARGS is a list of expected arguments for the method." ;; Cleanup. (dbus-unregister-service :session dbus--test-service))) +(ert-deftest dbus-test10-inhibitor-locks () + "Check `dbus-*-inhibitor-locks'." + :tags '(:expensive-test) + (skip-unless dbus--test-enabled-system-bus) + (skip-unless (dbus-ping :system dbus--test-systemd-service 1000)) + + (let (lock1 lock2) + ;; Create inhibitor lock. + (setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay")) + (should (natnump lock1)) + ;; The lock is reported by systemd. + (should + (member + (list "sleep" "Emacs" "Test delay" "delay" (user-uid) (emacs-pid)) + (dbus-call-method + :system dbus--test-systemd-service dbus--test-systemd-path + dbus--test-systemd-manager-interface "ListInhibitors"))) + ;; The lock is registered internally. + (should + (member + (list lock1 "sleep" "Test delay" nil) + (dbus-registered-inhibitor-locks))) + ;; There exist a file descriptor. + (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) + (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) + + ;; It is not possible to modify registered inhibitor locks on Lisp level. + (setcar (assoc lock1 (dbus-registered-inhibitor-locks)) 'malicious) + (should (assoc lock1 (dbus-registered-inhibitor-locks))) + (should-not (assoc 'malicious (dbus-registered-inhibitor-locks))) + + ;; Creating it again returns the same inhibitor lock. + (should (= lock1 (dbus-make-inhibitor-lock "sleep" "Test delay"))) + + ;; Create another inhibitor lock. + (setq lock2 (dbus-make-inhibitor-lock "sleep" "Test block" 'block)) + (should (natnump lock2)) + (should-not (= lock1 lock2)) + ;; The lock is reported by systemd. + (should + (member + (list "sleep" "Emacs" "Test block" "block" (user-uid) (emacs-pid)) + (dbus-call-method + :system dbus--test-systemd-service dbus--test-systemd-path + dbus--test-systemd-manager-interface "ListInhibitors"))) + ;; The lock is registered internally. + (should + (member + (list lock2 "sleep" "Test block" t) + (dbus-registered-inhibitor-locks))) + ;; There exist a file descriptor. + (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) + (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock2)))) + + ;; Close the first inhibitor lock. + (should (dbus-close-inhibitor-lock lock1)) + ;; The internal registration has gone. + (should-not + (member + (list lock1 "sleep" "Test delay" nil) + (dbus-registered-inhibitor-locks))) + ;; The file descriptor has been deleted. + (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) + (should-not (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) + + ;; Closing it again is a noop. + (should-not (dbus-close-inhibitor-lock lock1)) + + ;; Creating it again returns (another?) inhibitor lock. + (setq lock1 (dbus-make-inhibitor-lock "sleep" "Test delay")) + (should (natnump lock1)) + ;; The lock is registered internally. + (should + (member + (list lock1 "sleep" "Test delay" nil) + (dbus-registered-inhibitor-locks))) + ;; There exist a file descriptor. + (when (file-directory-p (format "/proc/%d/fd" (emacs-pid))) + (should (file-symlink-p (format "/proc/%d/fd/%d" (emacs-pid) lock1)))) + + ;; Close the inhibitor locks. + (should (dbus-close-inhibitor-lock lock1)) + (should (dbus-close-inhibitor-lock lock2)))) + (defun dbus-test-all (&optional interactive) "Run all tests for \\[dbus]." (interactive "p")