mirror of
git://git.sv.gnu.org/emacs.git
synced 2026-02-16 17:24:23 +00:00
system-sleep sleep blocker and sleep/wake event package (bug#80348)
This package provides platform-neutral interfaces to block your system from entering idle sleep and a hook to process pre-sleep and post-wake events. Implementations are for D-Bus on GNU/Linux, macOS/GNUstep, and MS-Windows. * lisp/system-sleep.el: New package. * src/fns.c: Qpre_sleep, Qpost_wake: New DEFSYM. * src/nsfns.m (Fns_block_system_sleep, Fns_unblock_system_sleep) (syms_of_nsfns): New functions. * src/nsterm.m (applicationDidFinishLaunching): Subscribe to pre-sleep and post-wake notifications. (systemWillSleep, systemDidWake): New function. * src/w32fns.c (Fw32_block_system_sleep) (Fw32_unblock_system_sleep, Fw32_system_sleep_block_count) (sleep_notification_callback) (w32_register_for_sleep_notifications): New function. (syms_of_w32fns): Sw32_unblock_system_sleep Sw32_block_system_sleep Sw32_system_sleep_block_count: New defsubr. * src/w32term.h (Fw32_block_system_sleep): New extern. * src/w32term.c (w32_initialize): Call w32_register_for_sleep_notifications. * doc/lispref/os.texi: Document the system-sleep package. * doc/lispref/commands.texi: Update sleep-event special documentation. * etc/NEWS: Announce the new package.
This commit is contained in:
parent
f13ab20f04
commit
0bee754a72
10 changed files with 876 additions and 14 deletions
|
|
@ -2778,12 +2778,12 @@ To test the signal handler, you can make Emacs send a signal to itself:
|
|||
@end smallexample
|
||||
|
||||
@cindex @code{sleep-event} event
|
||||
@item (sleep-event @var{sleep-wake})
|
||||
This event is injected when the device Emacs is running on enters or
|
||||
leaves the sleep state. A non-@code{nil} @var{sleep-wake} indicates
|
||||
entering the sleep state.
|
||||
@item (sleep-event @var{state})
|
||||
This event is injected when the device Emacs is running on is about to
|
||||
enter a sleep state, or has just awoken from one. @var{state} will be
|
||||
the symbol @code{pre-sleep} or @code{post-wake}.
|
||||
|
||||
This is implemented only on GNU/Linux.
|
||||
This is implemented on GNU/Linux, macOS, and MS-Windows.
|
||||
|
||||
@cindex @code{language-change} event
|
||||
@item language-change
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ terminal and the screen.
|
|||
* Session Management:: Saving and restoring state with X Session Management.
|
||||
* Desktop Notifications:: Desktop notifications.
|
||||
* System Taskbar:: Controlling system GUI taskbar features.
|
||||
* System Sleep:: Block system sleep and process sleep events.
|
||||
* File Notifications:: File notifications.
|
||||
* Dynamic Libraries:: On-demand loading of support libraries.
|
||||
* Security Considerations:: Running Emacs in an unfriendly environment.
|
||||
|
|
@ -3493,6 +3494,73 @@ Examples of system taskbar functions:
|
|||
@end group
|
||||
@end lisp
|
||||
|
||||
@node System Sleep
|
||||
@section Block System Sleep and Process Sleep Events
|
||||
@cindex system sleep
|
||||
@cindex mode, system sleep
|
||||
|
||||
@defun system-sleep-block-sleep &optional why allow-display-sleep
|
||||
This function blocks the system from entering its idle sleep state.
|
||||
|
||||
It returns a token that must be passed to
|
||||
@code{system-sleep-unblock-sleep} to unblock this specific block (other
|
||||
sleep blocks may be simultaneously in force for other purposes).
|
||||
Otherwise, it returns @code{nil} if the sleep blocking fails.
|
||||
|
||||
@var{why} is a string and, when non-nil, is used to identify the sleep
|
||||
block as it may appear on the system's inspectable block lists. It
|
||||
defaults to ``Emacs''.
|
||||
|
||||
If @var{allow-display-sleep} is non-nil, allow the display to sleep. By
|
||||
default, the display is kept active.
|
||||
|
||||
Note: When the Emacs process dies, blocks are released on all platforms.
|
||||
@end defun
|
||||
|
||||
@defun system-sleep-unblock-sleep token
|
||||
This function unblocks the sleep block associated with @var{token}. It
|
||||
returns non-@code{nil} on success, otherwise returns @code{nil}.
|
||||
@end defun
|
||||
|
||||
@defmac with-system-sleep-block (&optional why allow-display-sleep) body@dots{}
|
||||
This is a convenience macro that lets you wrap the forms in @var{body}
|
||||
with a sleep block that is unblocked for you when @var{body} completes.
|
||||
The arguments have the same meaning as in
|
||||
@code{system-sleep-block-sleep}, above.
|
||||
@end defmac
|
||||
|
||||
@defun system-sleep-sleep-blocked-p
|
||||
This predicate function returns non-@code{nil} on if there are any
|
||||
active @code{system-sleep} blocks, otherwise returns @code{nil}.
|
||||
@end defun
|
||||
|
||||
@defun system-sleep-unblock-all-sleep-blocks
|
||||
This function unblocks all active sleep blocks. It is unlikely that you
|
||||
will need to call this function.
|
||||
@end defun
|
||||
|
||||
@defopt system-sleep-event-functions
|
||||
When the system is about to enter a sleep state or after it wakes from
|
||||
one, each function on this abnormal hook is called with one argument,
|
||||
@var{event}, a sleep event. Its state can be retrieved via
|
||||
@samp{@code{(sleep-event-state event)}}. State will be one of the
|
||||
symbols @code{pre-sleep} or @code{post-wake}.
|
||||
|
||||
Handling @code{pre-sleep} events should be done as fast as possible and
|
||||
avoid user prompting. Systems often grant a very short pre-sleep
|
||||
processing interval, typically ranging between 2 and 5 seconds. The
|
||||
system may sleep even if your processing is not complete, so be sure you
|
||||
do as little as possible. For example, your function could close active
|
||||
connections or serial ports.
|
||||
|
||||
Handling @code{post-wake} events offers more leeway. Use this, for
|
||||
example, to reestablish connections.
|
||||
|
||||
Note: Your code, or the functions it calls, should not raise any signals
|
||||
or all hooks will be halted. You can wrap your code in a
|
||||
@code{condition-case} block (@pxref{Errors}).
|
||||
@end defopt
|
||||
|
||||
@node File Notifications
|
||||
@section Notifications on File Changes
|
||||
@cindex file notifications
|
||||
|
|
|
|||
16
etc/NEWS
16
etc/NEWS
|
|
@ -3597,6 +3597,22 @@ On GNU/Linux systems, shell extensions or similar helpers such as
|
|||
<https://extensions.gnome.org/extension/307/dash-to-dock/> and
|
||||
<https://wiki.ubuntu.com/Unity/LauncherAPI>.
|
||||
|
||||
+++
|
||||
** New package 'system-sleep'.
|
||||
This package provides platform-neutral interfaces to block your system
|
||||
from entering idle sleep and a hook to process pre-sleep and post-wake
|
||||
events. You can use this to avoid the system entering an idle sleep
|
||||
state and interrupting a long-running process due to lack of user
|
||||
activity. The sleep event hook lets you, for example close external
|
||||
connections or serial ports before sleeping, and reestablish them when
|
||||
the system wakes up.
|
||||
|
||||
Supported capable systems are GNU/Linux via D-Bus (sleep blocking and
|
||||
events require the org.freedesktop.login1 service, display sleep
|
||||
blocking requires org.freedesktop.Screensaver service), macOS
|
||||
(sleep/display blocking requires 10.9+, sleep events are supported on
|
||||
all versions), MS-Windows (sleep blocking is supported on all versions,
|
||||
sleep events require 8+).
|
||||
|
||||
* Incompatible Lisp Changes in Emacs 31.1
|
||||
|
||||
|
|
|
|||
513
lisp/system-sleep.el
Normal file
513
lisp/system-sleep.el
Normal file
|
|
@ -0,0 +1,513 @@
|
|||
;;; system-sleep.el --- System sleep/wake event management -*- lexical-binding: t -*-
|
||||
|
||||
;; Copyright (C) 2025-2026 Free Software Foundation, Inc.
|
||||
|
||||
;; Author: Stephane Marks <shipmints@gmail.com>
|
||||
;; Maintainer: emacs-devel@gnu.org
|
||||
;; Keywords: convenience
|
||||
;; Package-Requires: ((emacs "31.1"))
|
||||
|
||||
;; 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:
|
||||
|
||||
;; Call `system-sleep-block-sleep' to inhibit system-wide idle sleep.
|
||||
;; Idle sleep is typically triggered when the system does not detect
|
||||
;; user activity and is independent of any processing that may be on
|
||||
;; going. This function is useful to block idle sleep for long-running
|
||||
;; operations, for example, when a compilation is running. You have the
|
||||
;; option of keeping the system active while letting the display sleep.
|
||||
;; This function returns a token which you must use to unblock this
|
||||
;; request.
|
||||
;;
|
||||
;; Call `system-sleep-unblock-sleep' with the token from
|
||||
;; `system-sleep-block-sleep' to unblock system-wide idle sleep for this
|
||||
;; request. There may be other active requests which will prevent the
|
||||
;; system from sleeping.
|
||||
;;
|
||||
;; The function `system-sleep-sleep-blocked-p' will tell you if
|
||||
;; `system-sleep' has any active system sleep blocks.
|
||||
;;
|
||||
;; Note: When the Emacs process dies, blocks are released on all
|
||||
;; platforms.
|
||||
;;
|
||||
;; You can register functions on the abnormal hook
|
||||
;; `system-sleep-event-functions'. Each function will be called when
|
||||
;; the system is preparing for sleep and when the system wakes from
|
||||
;; sleep. These functions are useful when you want to close (and
|
||||
;; potentially reopen) external connections or serial ports.
|
||||
;;
|
||||
;; On supported GNU/Linux systems, the implementation is via D-Bus to
|
||||
;; inhibit idle sleep, keep the display active, and forward events from
|
||||
;; logind for system sleep events.
|
||||
;;
|
||||
;; On macOS and MS-Windows, native APIs are used to block idle sleep,
|
||||
;; keep the display active, and provide sleep event notifications.
|
||||
;;
|
||||
;; On MS-Windows, an idle sleep block that keeps the display active may
|
||||
;; not inhibit the screen saver.
|
||||
;;
|
||||
;; Externally to Emacs, there are system utility functions that you can
|
||||
;; use to inspect all processes on your system that might be blocking it
|
||||
;; from sleeping.
|
||||
;;
|
||||
;; On D-Bus systems, you can use the commands:
|
||||
;;
|
||||
;; systemd-inhibit --list
|
||||
;; or
|
||||
;; dbus-send --system --print-reply --dest=org.freedesktop.login1 \
|
||||
;; /org/freedesktop/login1 \
|
||||
;; org.freedesktop.login1.Manager.ListInhibitors
|
||||
;;
|
||||
;; Note: You can find the sleep/shutdown delay InhibitDelayMaxUSec in
|
||||
;; the file logind.conf(5) which typically defaults to 5 seconds.
|
||||
;;
|
||||
;; On macOS, you can use the command:
|
||||
;;
|
||||
;; pmset -g assertions
|
||||
;;
|
||||
;; On MS-Windows, you can use the following command which may need to be
|
||||
;; run as an administrator:
|
||||
;;
|
||||
;; powercfg -requests
|
||||
|
||||
;;; Code:
|
||||
|
||||
(require 'cl-lib)
|
||||
|
||||
;; Pacify the byte compiler.
|
||||
(declare-function dbus--fd-close "dbusbind.c")
|
||||
(declare-function dbus-unregister-object "dbus.el")
|
||||
(declare-function dbus-register-signal "dbus.el")
|
||||
(declare-function dbus-call-method "dbus.el")
|
||||
(declare-function dbus-list-activatable-names "dbus.el")
|
||||
(defvar dbus-service-emacs)
|
||||
|
||||
(defgroup system-sleep nil
|
||||
"System sleep/wake blocking and event management."
|
||||
:group 'system-interface
|
||||
:version "31.1")
|
||||
|
||||
(defvar system-sleep--back-end nil
|
||||
"Generic sleep-wake method system dispatcher.")
|
||||
|
||||
(defvar system-sleep--sleep-block-tokens nil
|
||||
"A list of active sleep-block tokens.
|
||||
If non-nil, idle sleep is inhibited by `system-sleep'.")
|
||||
|
||||
(cl-defstruct
|
||||
(sleep-event (:type list) :named
|
||||
(:constructor nil)
|
||||
(:constructor make-sleep-event (state)))
|
||||
state)
|
||||
|
||||
;;;###autoload
|
||||
(defcustom system-sleep-event-functions nil
|
||||
"Abnormal hook invoked on system sleep events.
|
||||
Each function is called with one argument EVENT, a sleep event. EVENT
|
||||
state can be retrieved via \\+`(sleep-event-state EVENT)'. It will be
|
||||
one of the symbols \\+`pre-sleep' or \\+`post-wake'.
|
||||
|
||||
Handling \\+`pre-sleep' events should be done as fast as possible, do as
|
||||
little as possible and avoid user prompts. Systems often grant a very
|
||||
short pre-sleep processing interval, typically ranging between 2 and 5
|
||||
seconds. The system may sleep even if your processing is not complete.
|
||||
For example, your function could close active connections or serial
|
||||
ports.
|
||||
|
||||
Handling \\+`post-wake' events offers more leeway. Your function could
|
||||
reestablish connections.
|
||||
|
||||
Note: Your code, or the functions it calls, should not raise any signals
|
||||
or all hooks will be halted preventing other hook functions from
|
||||
cleaning up or waking up. You can wrap your code in a `condition-case'
|
||||
block."
|
||||
:type 'hook
|
||||
:version "31.1")
|
||||
|
||||
;;;###autoload
|
||||
(defun system-sleep-block-sleep (&optional why allow-display-sleep)
|
||||
"Inhibit system idle sleep.
|
||||
Optional WHY is a string that identifies a sleep block to system utility
|
||||
commands that inspect system-wide blocks. WHY defaults to \"Emacs\".
|
||||
|
||||
Optional ALLOW-DISPLAY-SLEEP, when non-nil, allows the display to sleep
|
||||
or a screen saver to run while the system idle sleep is blocked. The
|
||||
default is to keep the display active.
|
||||
|
||||
Return a sleep blocking token. You must retain this value and provide
|
||||
it to `system-sleep-unblock-sleep' to unblock its associated block.
|
||||
|
||||
Return nil if system sleep cannot be inhibited.
|
||||
|
||||
Note: All active blocks are released when the Emacs process dies.
|
||||
Despite this, you should unblock your blocks when your processing is
|
||||
complete. See `with-system-sleep-block' for an easy way to do that."
|
||||
(when system-sleep--back-end
|
||||
(system-sleep--block-sleep (or why "Emacs") allow-display-sleep)))
|
||||
|
||||
(defun system-sleep-unblock-sleep (token)
|
||||
"Unblock the system sleep block associated with TOKEN.
|
||||
Return non-nil TOKEN was unblocked, or nil if not.
|
||||
In the unlikely event that unblock fails, the block will be released
|
||||
when the Emacs process dies."
|
||||
(when system-sleep--back-end
|
||||
(system-sleep--unblock-sleep token)))
|
||||
|
||||
;;;###autoload
|
||||
(defmacro with-system-sleep-block (&optional why allow-display-sleep &rest body)
|
||||
"Execute the forms in BODY while blocking system sleep.
|
||||
The optional arguments WHY and ALLOW-DISPLAY-SLEEP have the same meaning
|
||||
as in `system-sleep-block-sleep', which see.
|
||||
The block is unblocked when BODY completes."
|
||||
(declare (indent 1) (debug t))
|
||||
(let ((token (make-symbol "--sleep-token--")))
|
||||
`(let ((,token (system-sleep-block-sleep ,why ,allow-display-sleep)))
|
||||
(unwind-protect
|
||||
(progn
|
||||
,@body)
|
||||
(system-sleep-unblock-sleep ,token)))))
|
||||
|
||||
(defun system-sleep-unblock-all-sleep-blocks ()
|
||||
"Unblock all `system-sleep' blocks."
|
||||
(while system-sleep--sleep-block-tokens
|
||||
(system-sleep-unblock-sleep (car system-sleep--sleep-block-tokens))))
|
||||
|
||||
;;;###autoload
|
||||
(defun system-sleep-sleep-blocked-p ()
|
||||
"Return non-nil if there are active sleep blocks."
|
||||
(and system-sleep--back-end
|
||||
system-sleep--sleep-block-tokens))
|
||||
|
||||
|
||||
;; Internal implementation.
|
||||
|
||||
(defun system-sleep--set-back-end ()
|
||||
"Determine sleep/wake host system type."
|
||||
;; Order matters to accommodate the cases where an NS or MS-Windows
|
||||
;; build have the dbus feature.
|
||||
(setq system-sleep--back-end
|
||||
(cond ((featurep 'ns) 'ns)
|
||||
((featurep 'w32) 'w32)
|
||||
((and (require 'dbus)
|
||||
(featurep 'dbusbind)
|
||||
(member "org.freedesktop.login1"
|
||||
(dbus-list-activatable-names :system)))
|
||||
'dbus)
|
||||
(t nil))))
|
||||
|
||||
(defun system-sleep--sleep-event-handler (event)
|
||||
"`sleep-event' EVENT handler."
|
||||
(declare (completion ignore))
|
||||
(interactive "e")
|
||||
(run-hook-with-args 'system-sleep-event-functions event))
|
||||
|
||||
(defun system-sleep-enable ()
|
||||
"Enable `system-sleep'."
|
||||
(unless system-sleep--back-end
|
||||
(if (and (system-sleep--set-back-end)
|
||||
(system-sleep--enable))
|
||||
(keymap-set special-event-map "<sleep-event>"
|
||||
#'system-sleep--sleep-event-handler)
|
||||
(warn "`system-sleep' could not be initialized"))))
|
||||
|
||||
(defun system-sleep-disable ()
|
||||
"Disable `system-sleep'."
|
||||
(when system-sleep--back-end
|
||||
(keymap-set special-event-map "<sleep-event>" #'ignore)
|
||||
(system-sleep-unblock-all-sleep-blocks)
|
||||
(system-sleep--disable)
|
||||
(setq system-sleep--back-end nil)))
|
||||
|
||||
(cl-defgeneric system-sleep--enable ()
|
||||
"Enable the `system-sleep' back end.
|
||||
Return t if the back end is initialized, or nil.")
|
||||
|
||||
(cl-defgeneric system-sleep--disable ()
|
||||
"Disable the sleep/wake back end.")
|
||||
|
||||
(cl-defgeneric system-sleep--block-sleep (why allow-display-sleep)
|
||||
"Inhibit system idle sleep.
|
||||
WHY is a string that identifies a sleep block to system utility commands
|
||||
that inspect system-wide blocks.
|
||||
When non-nil, ALLOW-DISPLAY-SLEEP allows the display to sleep or a
|
||||
screen saver to run while the system idle sleep is blocked. The default
|
||||
is to keep the display active.
|
||||
Return a sleep-block token.")
|
||||
|
||||
(cl-defgeneric system-sleep--unblock-sleep (token)
|
||||
"Unblock the system sleep block associated with TOKEN.
|
||||
Return non-nil TOKEN was unblocked, or nil if not.")
|
||||
|
||||
(defvar system-sleep--event-in-progress nil)
|
||||
(defvar system-sleep--event-queue nil)
|
||||
|
||||
(defun system-sleep--sleep-event-function (event)
|
||||
"Handle <sleep-event> special events and avoid races."
|
||||
;; Queue incoming event.
|
||||
(setq system-sleep--event-queue
|
||||
(append system-sleep--event-queue (list event)))
|
||||
;; If an event is already in progress, return right away.
|
||||
;; Otherwise, process queued events.
|
||||
(while (and (not system-sleep--event-in-progress)
|
||||
system-sleep--event-queue)
|
||||
(let ((current-event (pop system-sleep--event-queue)))
|
||||
(setq system-sleep--event-in-progress current-event)
|
||||
(unwind-protect
|
||||
(run-hook-with-args 'system-sleep-event-functions
|
||||
current-event)
|
||||
(setq system-sleep--event-in-progress nil)))))
|
||||
|
||||
|
||||
;; D-Bus support.
|
||||
|
||||
(defvar system-sleep--dbus-sleep-inhibitor-types "sleep"
|
||||
"This is a colon-separated list of options.
|
||||
The default is \"sleep\" which is compatible with the other supported
|
||||
`system-sleep' platforms. This could also be
|
||||
\"sleep:shutdown\". Shutdown is available only on D-Bus systems.")
|
||||
|
||||
(defvar system-sleep--dbus-delay-lock nil)
|
||||
(defvar system-sleep--dbus-pre-sleep-signal nil)
|
||||
|
||||
(defun system-sleep--dbus-delay-lock (make-or-close)
|
||||
(cond (make-or-close
|
||||
(if system-sleep--dbus-delay-lock
|
||||
(error "Delay lock should be nil")
|
||||
(setq system-sleep--dbus-delay-lock
|
||||
(dbus-call-method
|
||||
:system
|
||||
"org.freedesktop.login1"
|
||||
"/org/freedesktop/login1"
|
||||
"org.freedesktop.login1.Manager"
|
||||
"Inhibit"
|
||||
:keep-fd
|
||||
system-sleep--dbus-sleep-inhibitor-types
|
||||
dbus-service-emacs
|
||||
"Emacs sleep event watcher"
|
||||
"delay"))))
|
||||
(t
|
||||
(when system-sleep--dbus-delay-lock
|
||||
(dbus--fd-close system-sleep--dbus-delay-lock)
|
||||
(setq system-sleep--dbus-delay-lock nil)))))
|
||||
|
||||
(defun system-sleep--dbus-prepare-for-sleep-callback (sleep-or-wake)
|
||||
(cond (sleep-or-wake
|
||||
(insert-special-event (make-sleep-event 'pre-sleep)))
|
||||
(t
|
||||
(insert-special-event (make-sleep-event 'post-wake)))))
|
||||
|
||||
(defun system-sleep--dbus-prepare-for-sleep-watcher (make-or-close)
|
||||
(cond (make-or-close
|
||||
(if system-sleep--dbus-pre-sleep-signal
|
||||
(error "PrepareForSleep watcher should be nil")
|
||||
(setq system-sleep--dbus-pre-sleep-signal
|
||||
(dbus-register-signal
|
||||
:system
|
||||
"org.freedesktop.login1"
|
||||
"/org/freedesktop/login1"
|
||||
"org.freedesktop.login1.Manager"
|
||||
"PrepareForSleep"
|
||||
#'system-sleep--dbus-prepare-for-sleep-callback))))
|
||||
(t
|
||||
(dbus-unregister-object system-sleep--dbus-pre-sleep-signal)
|
||||
(setq system-sleep--dbus-pre-sleep-signal nil))))
|
||||
|
||||
(defun system-sleep--dbus-prepare-for-sleep-function (event)
|
||||
(pcase (sleep-event-state event)
|
||||
('pre-sleep
|
||||
(system-sleep--dbus-delay-lock nil))
|
||||
('post-wake
|
||||
(system-sleep--dbus-delay-lock t))))
|
||||
|
||||
(cl-defmethod system-sleep--enable (&context
|
||||
(system-sleep--back-end (eql 'dbus)))
|
||||
;; Order matters.
|
||||
(add-hook 'system-sleep-event-functions
|
||||
#'system-sleep--dbus-prepare-for-sleep-function
|
||||
;; This must run last.
|
||||
99)
|
||||
(system-sleep--dbus-delay-lock t)
|
||||
(system-sleep--dbus-prepare-for-sleep-watcher t)
|
||||
t)
|
||||
|
||||
(cl-defmethod system-sleep--disable (&context
|
||||
(system-sleep--back-end (eql 'dbus)))
|
||||
(system-sleep--dbus-prepare-for-sleep-watcher nil)
|
||||
(system-sleep--dbus-delay-lock nil)
|
||||
(remove-hook 'system-sleep-event-functions
|
||||
#'system-sleep--dbus-prepare-for-sleep-function))
|
||||
|
||||
(cl-defmethod system-sleep--block-sleep (why
|
||||
allow-display-sleep
|
||||
&context
|
||||
(system-sleep--back-end (eql 'dbus)))
|
||||
(let ((subtokens))
|
||||
(if-let* ((sleep-cookie (dbus-call-method
|
||||
:system
|
||||
"org.freedesktop.login1"
|
||||
"/org/freedesktop/login1"
|
||||
"org.freedesktop.login1.Manager"
|
||||
"Inhibit"
|
||||
:keep-fd
|
||||
system-sleep--dbus-sleep-inhibitor-types
|
||||
dbus-service-emacs
|
||||
why
|
||||
"block")))
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(push (cons 'dbus-inhibitor-lock sleep-cookie) subtokens))
|
||||
(unless allow-display-sleep
|
||||
(if-let* ((screen-cookie
|
||||
(dbus-call-method
|
||||
:session
|
||||
"org.freedesktop.ScreenSaver"
|
||||
"/org/freedesktop/ScreenSaver"
|
||||
"org.freedesktop.ScreenSaver"
|
||||
"Inhibit"
|
||||
dbus-service-emacs
|
||||
"Screen Saver Block")))
|
||||
(let ((inhibit-quit t))
|
||||
(push (cons 'dbus-screensaver-lock screen-cookie) subtokens))
|
||||
(warn "Unable to block the screen saver")))
|
||||
(let ((inhibit-quit t))
|
||||
(let ((token (list :system 'dbus :why why :subtokens subtokens)))
|
||||
(push token system-sleep--sleep-block-tokens)
|
||||
token)))
|
||||
(warn "Unable to block system sleep"))))
|
||||
|
||||
(cl-defmethod system-sleep--unblock-sleep (token
|
||||
&context
|
||||
(system-sleep--back-end (eql 'dbus)))
|
||||
|
||||
(if (memq token system-sleep--sleep-block-tokens)
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(setq system-sleep--sleep-block-tokens
|
||||
(remq token system-sleep--sleep-block-tokens)))
|
||||
(dolist (subtoken (plist-get token :subtokens))
|
||||
(pcase (car subtoken)
|
||||
('dbus-inhibitor-lock
|
||||
(dbus--fd-close (cdr subtoken)))
|
||||
('dbus-screensaver-lock
|
||||
(dbus-call-method
|
||||
:session
|
||||
"org.freedesktop.ScreenSaver"
|
||||
"/org/freedesktop/ScreenSaver"
|
||||
"org.freedesktop.ScreenSaver"
|
||||
"UnInhibit"
|
||||
(cdr subtoken)))))
|
||||
t)
|
||||
(warn "Unknown `system-sleep' sleep token")
|
||||
nil))
|
||||
|
||||
|
||||
;; macOS/GNUstep NS support.
|
||||
|
||||
(declare-function ns-block-system-sleep "nsfns.m")
|
||||
(declare-function ns-unblock-system-sleep "nsfns.m")
|
||||
|
||||
(cl-defmethod system-sleep--enable (&context
|
||||
(system-sleep--back-end (eql 'ns)))
|
||||
t)
|
||||
|
||||
(cl-defmethod system-sleep--disable (&context
|
||||
(system-sleep--back-end (eql 'ns)))
|
||||
(ignore))
|
||||
|
||||
(cl-defmethod system-sleep--block-sleep (why
|
||||
allow-display-sleep
|
||||
&context
|
||||
(system-sleep--back-end (eql 'ns)))
|
||||
(if-let* ((cookie (ns-block-system-sleep why allow-display-sleep))
|
||||
(token (list :system 'ns :why why
|
||||
:token (cons 'ns-sleep-block cookie))))
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(push token system-sleep--sleep-block-tokens))
|
||||
token)
|
||||
(warn "Unable to block system sleep")))
|
||||
|
||||
(cl-defmethod system-sleep--unblock-sleep (token
|
||||
&context
|
||||
(system-sleep--back-end (eql 'ns)))
|
||||
(if (memq token system-sleep--sleep-block-tokens)
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(setq system-sleep--sleep-block-tokens
|
||||
(remq token system-sleep--sleep-block-tokens)))
|
||||
(if (ns-unblock-system-sleep (cdr (plist-get token :token)))
|
||||
t
|
||||
(warn "Unable to unblock system sleep (blocks are released when Emacs dies)")
|
||||
nil))
|
||||
(warn "Unknown `system-sleep' sleep token")
|
||||
nil))
|
||||
|
||||
|
||||
;; MS-Windows support.
|
||||
|
||||
(declare-function w32-block-system-sleep "w32fns.c")
|
||||
(declare-function w32-unblock-system-sleep "w32fns.c")
|
||||
(declare-function w32-system-sleep-block-count "w32fns.c")
|
||||
|
||||
(defvar system-sleep--w32-sleep-block-count 0)
|
||||
|
||||
(cl-defmethod system-sleep--enable (&context
|
||||
(system-sleep--back-end (eql 'w32)))
|
||||
t)
|
||||
|
||||
(cl-defmethod system-sleep--disable (&context
|
||||
(system-sleep--back-end (eql 'w32)))
|
||||
(ignore))
|
||||
|
||||
(cl-defmethod system-sleep--block-sleep (why
|
||||
allow-display-sleep
|
||||
&context
|
||||
(system-sleep--back-end (eql 'w32)))
|
||||
(if-let* ((cookie (w32-block-system-sleep allow-display-sleep))
|
||||
(token (list :system 'w32 :why why
|
||||
:token (cons 'w32-sleep-block cookie))))
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(push token system-sleep--sleep-block-tokens))
|
||||
token)
|
||||
(warn "Unable to block system sleep")))
|
||||
|
||||
(cl-defmethod system-sleep--unblock-sleep (token
|
||||
&context
|
||||
(system-sleep--back-end (eql 'w32)))
|
||||
(if (memq token system-sleep--sleep-block-tokens)
|
||||
(progn
|
||||
(let ((inhibit-quit t))
|
||||
(setq system-sleep--sleep-block-tokens
|
||||
(remq token system-sleep--sleep-block-tokens)))
|
||||
(if (eq 0 (w32-system-sleep-block-count))
|
||||
(warn "Unable to unblock system sleep (no active tokens)")
|
||||
(if (w32-unblock-system-sleep)
|
||||
t
|
||||
(warn "Unable to unblock system sleep (blocks are released when Emacs dies)")
|
||||
nil)))
|
||||
(warn "Unknown `system-sleep' sleep token")
|
||||
nil))
|
||||
|
||||
|
||||
;; Initialize system-sleep.
|
||||
|
||||
(system-sleep-enable)
|
||||
|
||||
(provide 'system-sleep)
|
||||
|
||||
;;; system-sleep.el ends here
|
||||
|
|
@ -6891,4 +6891,8 @@ For best results this should end in a space. */);
|
|||
DEFSYM (QCin_place, ":in-place");
|
||||
DEFSYM (QCreverse, ":reverse");
|
||||
DEFSYM (Qvaluelt, "value<");
|
||||
|
||||
/* sleep-event states. */
|
||||
DEFSYM (Qpre_sleep, "pre-sleep");
|
||||
DEFSYM (Qpost_wake, "post-wake");
|
||||
}
|
||||
|
|
|
|||
84
src/nsfns.m
84
src/nsfns.m
|
|
@ -3805,6 +3805,88 @@ The position is returned as a cons cell (X . Y) of the
|
|||
return Qnil;
|
||||
}
|
||||
|
||||
/* A unique integer sleep block id and a hash map of its id to opaque
|
||||
NSObject sleep block activity tokens. */
|
||||
static unsigned int sleep_block_id = 0;
|
||||
static NSMutableDictionary *sleep_block_map = NULL;
|
||||
|
||||
DEFUN ("ns-block-system-sleep",
|
||||
Fns_block_system_sleep,
|
||||
Sns_block_system_sleep,
|
||||
2, 2, 0,
|
||||
doc: /* Block system idle sleep.
|
||||
WHY is a string reason for the block.
|
||||
If ALLOW-DISPLAY-SLEEP is non-nil, block the screen from sleeping.
|
||||
Return a token to unblock this block using `ns-unblock-system-sleep',
|
||||
or nil if the block fails. */)
|
||||
(Lisp_Object why, Lisp_Object allow_display_sleep)
|
||||
{
|
||||
block_input ();
|
||||
|
||||
NSString *reason = @"Emacs";
|
||||
if (!NILP (why))
|
||||
{
|
||||
CHECK_STRING (why);
|
||||
reason = [NSString stringWithLispString: why];
|
||||
}
|
||||
|
||||
unsigned long activity_options =
|
||||
NSActivityUserInitiated | NSActivityIdleSystemSleepDisabled;
|
||||
if (NILP (allow_display_sleep))
|
||||
activity_options |= NSActivityIdleDisplaySleepDisabled;
|
||||
|
||||
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
|
||||
NSObject *activity_id = nil;
|
||||
if ([processInfo respondsToSelector:@selector(beginActivityWithOptions:reason:)])
|
||||
activity_id = [[NSProcessInfo processInfo]
|
||||
beginActivityWithOptions: activity_options
|
||||
reason: reason];
|
||||
unblock_input ();
|
||||
|
||||
if (!sleep_block_map)
|
||||
sleep_block_map = [[NSMutableDictionary alloc] initWithCapacity: 25];
|
||||
|
||||
if (activity_id)
|
||||
{
|
||||
[sleep_block_map setObject: activity_id
|
||||
forKey: [NSNumber numberWithInt: ++sleep_block_id]];
|
||||
return make_fixnum (sleep_block_id);
|
||||
}
|
||||
else
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
DEFUN ("ns-unblock-system-sleep",
|
||||
Fns_unblock_system_sleep,
|
||||
Sns_unblock_system_sleep,
|
||||
1, 1, 0,
|
||||
doc: /* Unblock system idle sleep.
|
||||
TOKEN is an object returned by `ns-block-system-sleep'.
|
||||
Return non-nil if the TOKEN block was unblocked. */)
|
||||
(Lisp_Object token)
|
||||
{
|
||||
block_input ();
|
||||
Lisp_Object res = Qnil;
|
||||
CHECK_FIXNAT (token);
|
||||
if (sleep_block_map)
|
||||
{
|
||||
NSNumber *key = [NSNumber numberWithInt: XFIXNAT (token)];
|
||||
NSObject *activity_id = [sleep_block_map objectForKey: key];
|
||||
if (activity_id)
|
||||
{
|
||||
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
|
||||
if ([processInfo respondsToSelector:@selector(endActivity:)])
|
||||
{
|
||||
[[NSProcessInfo processInfo] endActivity: activity_id];
|
||||
res = Qt;
|
||||
}
|
||||
[sleep_block_map removeObjectForKey: key];
|
||||
}
|
||||
}
|
||||
unblock_input ();
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
|
||||
DEFUN ("ns-send-items",
|
||||
|
|
@ -4091,6 +4173,8 @@ - (Lisp_Object)lispString
|
|||
defsubr (&Sns_badge);
|
||||
defsubr (&Sns_request_user_attention);
|
||||
defsubr (&Sns_progress_indicator);
|
||||
defsubr (&Sns_block_system_sleep);
|
||||
defsubr (&Sns_unblock_system_sleep);
|
||||
#ifdef NS_IMPL_COCOA
|
||||
defsubr (&Sns_send_items);
|
||||
#endif
|
||||
|
|
|
|||
48
src/nsterm.m
48
src/nsterm.m
|
|
@ -5838,15 +5838,6 @@ static Lisp_Object ns_new_font (struct frame *f, Lisp_Object font_object,
|
|||
ns_pending_service_names = [[NSMutableArray alloc] init];
|
||||
ns_pending_service_args = [[NSMutableArray alloc] init];
|
||||
|
||||
#if defined (NS_IMPL_COCOA) && MAC_OS_X_VERSION_MAX_ALLOWED >= 260000
|
||||
/* Disable problematic event processing on macOS 26 (Tahoe) to avoid
|
||||
scrolling lag and input handling issues. These are undocumented
|
||||
options as of macOS 26.0. */
|
||||
[NSUserDefaults.standardUserDefaults
|
||||
registerDefaults:@{@"NSEventConcurrentProcessingEnabled" : @"NO",
|
||||
@"NSApplicationUpdateCycleEnabled" : @"NO"}];
|
||||
#endif
|
||||
|
||||
/* Start app and create the main menu, window, view.
|
||||
Needs to be here because ns_initialize_display_info () uses AppKit classes.
|
||||
The view will then ask the NSApp to stop and return to Emacs. */
|
||||
|
|
@ -6383,6 +6374,20 @@ - (void)applicationDidFinishLaunching: (NSNotification *)notification
|
|||
object:nil];
|
||||
#endif
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
/* Sleep event notification. */
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserver: self
|
||||
selector:@selector(systemWillSleep:)
|
||||
name: NSWorkspaceWillSleepNotification
|
||||
object: nil];
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserver: self
|
||||
selector: @selector(systemDidWake:)
|
||||
name: NSWorkspaceDidWakeNotification
|
||||
object: nil];
|
||||
#endif
|
||||
|
||||
#ifdef NS_IMPL_COCOA
|
||||
/* Some functions/methods in CoreFoundation/Foundation increase the
|
||||
maximum number of open files for the process in their first call.
|
||||
|
|
@ -6421,6 +6426,31 @@ - (void)antialiasThresholdDidChange:(NSNotification *)notification
|
|||
#endif
|
||||
}
|
||||
|
||||
/* Sleep event notification. */
|
||||
|
||||
- (void) systemWillSleep:(NSNotification *)notification
|
||||
{
|
||||
#ifdef NS_IMPL_COCOA
|
||||
NSTRACE ("[EmacsApp systemWillSleep:]");
|
||||
struct input_event ie;
|
||||
EVENT_INIT (ie);
|
||||
ie.kind = SLEEP_EVENT;
|
||||
ie.arg = list1 (Qpre_sleep);
|
||||
kbd_buffer_store_event (&ie);
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void) systemDidWake:(NSNotification *)notification
|
||||
{
|
||||
#ifdef NS_IMPL_COCOA
|
||||
NSTRACE ("[EmacsApp systemDidWake:]");
|
||||
struct input_event ie;
|
||||
EVENT_INIT (ie);
|
||||
ie.kind = SLEEP_EVENT;
|
||||
ie.arg = list1 (Qpost_wake);
|
||||
kbd_buffer_store_event (&ie);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Termination sequences:
|
||||
C-x C-c:
|
||||
|
|
|
|||
145
src/w32fns.c
145
src/w32fns.c
|
|
@ -11325,6 +11325,136 @@ if the selected frame is not (yet) associated with a window handle */)
|
|||
|
||||
#endif /* WINDOWSNT */
|
||||
|
||||
/***********************************************************************
|
||||
System Sleep Support
|
||||
***********************************************************************/
|
||||
|
||||
typedef ULONG (WINAPI * SetThreadExecutionState_Proc)
|
||||
(IN ULONG);
|
||||
static SetThreadExecutionState_Proc SetThreadExecutionState_fn = NULL;
|
||||
|
||||
static unsigned int sleep_block_id = 0;
|
||||
static unsigned int sleep_block_count = 0;
|
||||
|
||||
DEFUN ("w32-block-system-sleep",
|
||||
Fw32_block_system_sleep,
|
||||
Sw32_block_system_sleep,
|
||||
1, 1, 0,
|
||||
doc: /* Block system idle sleep.
|
||||
If ALLOW-DISPLAY-SLEEP is non-nil, block the screen from sleeping.
|
||||
Return a token to unblock this block using `w32-unblock-system-sleep',
|
||||
or nil if the block fails. */)
|
||||
(Lisp_Object allow_display_sleep)
|
||||
{
|
||||
if (SetThreadExecutionState_fn == NULL)
|
||||
return Qnil;
|
||||
|
||||
/* ES_CONTINUOUS keeps the state until cleared. */
|
||||
EXECUTION_STATE new_state = ES_SYSTEM_REQUIRED | ES_CONTINUOUS;
|
||||
if (NILP (allow_display_sleep))
|
||||
new_state |= ES_DISPLAY_REQUIRED;
|
||||
|
||||
if (SetThreadExecutionState (new_state) == 0)
|
||||
return Qnil;
|
||||
else
|
||||
{
|
||||
/* One more block and next id. */
|
||||
++sleep_block_count;
|
||||
++sleep_block_id;
|
||||
|
||||
/* Synthesize a token. */
|
||||
return make_fixnum (sleep_block_id);
|
||||
}
|
||||
}
|
||||
|
||||
DEFUN ("w32-unblock-system-sleep",
|
||||
Fw32_unblock_system_sleep,
|
||||
Sw32_unblock_system_sleep,
|
||||
0, 0, 0,
|
||||
doc: /* Unblock system idle sleep.
|
||||
Return non-nil if the TOKEN block was unblocked. */)
|
||||
(void)
|
||||
{
|
||||
if (SetThreadExecutionState_fn == NULL)
|
||||
return Qnil;
|
||||
|
||||
/* No blocks to unblock. */
|
||||
if (sleep_block_count == 0)
|
||||
return Qnil;
|
||||
|
||||
/* One fewer block. */
|
||||
if (--sleep_block_count == 0
|
||||
&& SetThreadExecutionState (ES_CONTINUOUS) == 0)
|
||||
return Qnil;
|
||||
else
|
||||
return Qt;
|
||||
}
|
||||
|
||||
DEFUN ("w32-system-sleep-block-count",
|
||||
Fw32_system_sleep_block_count,
|
||||
Sw32_system_sleep_block_count,
|
||||
0, 0, 0,
|
||||
doc: /* Return the w32 sleep block count. */)
|
||||
(void)
|
||||
{
|
||||
return make_fixnum (sleep_block_count);
|
||||
}
|
||||
|
||||
typedef ULONG (CALLBACK *PMY_DEVICE_NOTIFY_CALLBACK_ROUTINE)
|
||||
(PVOID Context, ULONG Type, PVOID Setting);
|
||||
|
||||
static ULONG ALIGN_STACK
|
||||
sleep_notification_callback(PVOID _Context, ULONG Type, PVOID _Setting)
|
||||
{
|
||||
struct input_event ie;
|
||||
EVENT_INIT (ie);
|
||||
ie.kind = SLEEP_EVENT;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case PBT_APMRESUMEAUTOMATIC:
|
||||
/* Ignore this event. No user is present. */
|
||||
break;
|
||||
case PBT_APMSUSPEND:
|
||||
ie.arg = list1 (Qpre_sleep);
|
||||
kbd_buffer_store_event (&ie);
|
||||
break;
|
||||
case PBT_APMRESUMESUSPEND:
|
||||
ie.arg = list1 (Qpost_wake);
|
||||
kbd_buffer_store_event (&ie);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef HPOWERNOTIFY (WINAPI * RegisterSuspendResumeNotification_Proc)
|
||||
(IN HANDLE, IN DWORD);
|
||||
static RegisterSuspendResumeNotification_Proc RegisterSuspendResumeNotification_fn = NULL;
|
||||
|
||||
static HPOWERNOTIFY sleep_notification_handle = 0;
|
||||
|
||||
typedef struct _MY_DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS {
|
||||
PMY_DEVICE_NOTIFY_CALLBACK_ROUTINE Callback;
|
||||
PVOID Context;
|
||||
} MY_DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS, *PMY_DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS;
|
||||
|
||||
void
|
||||
w32_register_for_sleep_notifications()
|
||||
{
|
||||
/* PowerRegisterSuspendResumeNotification is not a user-space call so
|
||||
we use RegisterSuspendResumeNotification. */
|
||||
if (RegisterSuspendResumeNotification_fn)
|
||||
{
|
||||
MY_DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS params;
|
||||
params.Callback = sleep_notification_callback;
|
||||
params.Context = NULL;
|
||||
|
||||
/* DEVICE_NOTIFY_CALLBACK = 2 */
|
||||
sleep_notification_handle =
|
||||
RegisterSuspendResumeNotification_fn (¶ms, 2);
|
||||
}
|
||||
}
|
||||
|
||||
/***********************************************************************
|
||||
Initialization
|
||||
***********************************************************************/
|
||||
|
|
@ -11834,6 +11964,10 @@ keys when IME input is received. */);
|
|||
defsubr (&Sw32_request_user_attention);
|
||||
DEFSYM (Qinformational, "informational");
|
||||
DEFSYM (Qcritical, "critical");
|
||||
/* System sleep support. */
|
||||
defsubr (&Sw32_unblock_system_sleep);
|
||||
defsubr (&Sw32_block_system_sleep);
|
||||
defsubr (&Sw32_system_sleep_block_count);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -12094,6 +12228,7 @@ void
|
|||
globals_of_w32fns (void)
|
||||
{
|
||||
HMODULE user32_lib = GetModuleHandle ("user32.dll");
|
||||
HMODULE kernel32_lib = GetModuleHandle ("kernel32.dll");
|
||||
/*
|
||||
TrackMouseEvent not available in all versions of Windows, so must load
|
||||
it dynamically. Do it once, here, instead of every time it is used.
|
||||
|
|
@ -12120,6 +12255,16 @@ globals_of_w32fns (void)
|
|||
RegisterTouchWindow_fn
|
||||
= (RegisterTouchWindow_proc) get_proc_addr (user32_lib,
|
||||
"RegisterTouchWindow");
|
||||
/* For system sleep support. */
|
||||
SetThreadExecutionState_fn
|
||||
= (SetThreadExecutionState_Proc)
|
||||
get_proc_addr (kernel32_lib,
|
||||
"SetThreadExecutionState");
|
||||
RegisterSuspendResumeNotification_fn
|
||||
= (RegisterSuspendResumeNotification_Proc)
|
||||
get_proc_addr (user32_lib,
|
||||
"RegisterSuspendResumeNotification");
|
||||
|
||||
SetGestureConfig_fn
|
||||
= (SetGestureConfig_proc) get_proc_addr (user32_lib,
|
||||
"SetGestureConfig");
|
||||
|
|
|
|||
|
|
@ -8249,6 +8249,7 @@ w32_initialize (void)
|
|||
}
|
||||
|
||||
w32_get_mouse_wheel_vertical_delta ();
|
||||
w32_register_for_sleep_notifications ();
|
||||
}
|
||||
|
||||
void
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ extern const char *w32_get_string_resource (void *v_rdb,
|
|||
extern frame_parm_handler w32_frame_parm_handlers[];
|
||||
extern void w32_default_font_parameter (struct frame* f, Lisp_Object parms);
|
||||
extern Lisp_Object w32_process_dnd_data (int format, void *pDataObj);
|
||||
extern void w32_register_for_sleep_notifications();
|
||||
|
||||
|
||||
#define PIX_TYPE COLORREF
|
||||
|
|
|
|||
Loading…
Reference in a new issue