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

From: Andrew Cohen <[email-masked]>
Date: Thu, 06 Feb 2025 09:43:56 +0800

>>>>> "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