Subject: Re: bug#63620: 30.0.50; [Feature Request] run hooks on sleep/wake

From: Ship Mints <[email-masked]>
Date: Wed, 5 Feb 2025 21:06:33 -0500

If someone builds without dbus, say on macOS, does this approach work
without dbus?

On Wed, Feb 5, 2025 at 8:44 PM Andrew Cohen <[email-masked]> wrote:

> >>>>> "SM" == Stefan Monnier <[email-masked]> writes:
>
>     >>> Maybe a hook is not such a bad idea after all.
>     >> But then how would platforms where these events come from the
>     >> window-system call that hook?
>
>     SM> I don't understand the question: why would those not be ale to
>     SM> run the hook?  If it's because it's in a separate UI thread,
>     SM> they can push to `pending_funcalls`, or in the worst case they
>     SM> can add an event into the event queue and then use
>     SM> `special-event-map` to run an ad-hoc function which runs the
>     SM> hook.
>
> I remain open to other ideas, but it seems to me that the most
> straightforward is to allow systems to add a sleep-event into the event
> queue.  My original code installed a callback function to run the hooks
> when a state change is detected.  So this same function can be installed
> into special-event-map and the behavior on all systems is the same.
>
> I've modified the code a bit---I've made the handler function
> configurable with a defcustom and set it by default to the function that
> just runs the hooks. But the same handler will be used on both
> sleep-events and dbus detection.  The only difference is that if the
> system supports dbus, dbus detection of state changes will be used,
> while in other cases sleep-events will trigger the handler.
>
> ;;; sleep.el --- run hooks on device sleep and wake  -*- lexical-binding:t
> -*-
>
> ;; Copyright (C) 2025 Free Software Foundation, Inc.
>
> ;; Author: Andrew Cohen <>
> ;; Maintainer: [email-masked]
> ;; Keywords:
>
> ;; This file is part of GNU Emacs.
>
> ;; GNU Emacs 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.
>
> ;; GNU Emacs 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 GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
>
> ;;; Commentary:
>
> ;;; This global minor mode enables evaluating code when the device
> ;;; running Emacs enters or leaves the sleep state.  The hooks
> ;;; `sleep-sleep-hook' and `sleep-wake-hook' are run when the system
> ;;; detects that it is going to sleep or waking up.  Currently only a
> ;;; D-Bus interface to detect sleep state change is implemented.
>
> ;;; Code:
>
> (require 'dbus)
>
> (defgroup sleep nil
>   "Run hooks on device entering/leaving the sleep state."
>   :group 'hardware)
>
> (defcustom sleep-handler-function 'handle-sleep-event
>   "Function called on device sleep state change.
> The function takes a single boolean argument (t for sleep and nil for
> wake).  The default function just runs the sleep and wake hooks."
>   :group 'sleep
>   :type 'function)
>
> (defcustom sleep-sleep-hook nil
>   "Hook to run on device entering sleep."
>   :group 'sleep
>   :type 'hook)
>
> (defcustom sleep-wake-hook nil
>   "Hook to run on device leaving sleep."
>   :group 'sleep
>   :type 'hook)
>
> (defvar sleep-dbus-registration-object nil
>   "Object returned from `dbus-register-signal'.
> This is recorded so that the signal may be unregistered.")
>
> (defun sleep-dbus-detection (mode)
>   "Enable D-Bus detection of device sleep/wake state change.
> When enabled run `sleep-handler-function' when a state change is
> detected.  Disable detection if MODE is nil."
>   (if mode
>       (unless sleep-dbus-registration-object
>         (setq sleep-dbus-registration-object
>               (dbus-register-signal :system
>                                     "org.freedesktop.login1"
>                                     "/org/freedesktop/login1"
>                                     "org.freedesktop.login1.Manager"
>                                     "PrepareForSleep"
>                                     sleep-handler-function)))
>     (condition-case nil
>         (progn
>           (ignore-error (dbus-error wrong-type-argument)
>             (dbus-unregister-object
>              sleep-dbus-registration-object))
>           (setq sleep-dbus-registration-object nil))
>       (wrong-type-argument nil))))
>
> (defun handle-sleep-event (sleep-wake)
>   "Handler to execute sleep and wake functions.
> SLEEP-WAKE is t on sleeping and nil on waking."
>   (ignore-errors
>     (if sleep-wake
>         (run-hooks 'sleep-sleep-hook)
>       (run-hooks 'sleep-wake-hook))))
>
> ;;;###autoload
> (define-minor-mode sleep-wake-mode
>   "Toggle sleep/wake detection.
>
> With `sleep-wake-mode' enabled, the hooks `sleep-sleep-hook' and
> `sleep-wake-hook' will be executed when the device enters or leaves the
> sleep state.  This is currently only available on systems that support
> D-Bus detection of sleep state changes."
>   :global t
>   :group 'sleep
>   (cond
>    ((and (featurep 'dbusbind)
>          (member "org.freedesktop.login1"
>                  (dbus-list-activatable-names :system)))
>     (sleep-dbus-detection sleep-wake-mode))
>    (t
>     (if sleep-wake-mode
>         (define-key special-event-map [sleep-event] sleep-handler-function)
>       (define-key special-event-map [sleep-event] 'ignore)))))
>
> (provide 'sleep)
> ;;; sleep.el ends here
>
>
> --
> Andrew Cohen
>