diff options
author | Basil L. Contovounesios <contovob@tcd.ie> | 2020-06-11 13:49:31 +0100 |
---|---|---|
committer | Basil L. Contovounesios <contovob@tcd.ie> | 2020-06-18 13:11:17 +0100 |
commit | 453d30d92cbf940567869d4705c1fcfe57725825 (patch) | |
tree | d79b43fdae2af987c36f018635e0c74bd1f67b53 /lisp/battery.el | |
parent | 23a148c9506f2a5bce71bd5c8822bb7cde6697e8 (diff) | |
download | emacs-453d30d92cbf940567869d4705c1fcfe57725825.tar.gz emacs-453d30d92cbf940567869d4705c1fcfe57725825.tar.bz2 emacs-453d30d92cbf940567869d4705c1fcfe57725825.zip |
Improve battery.el UPower support
For discussion, see the following threads:
https://lists.gnu.org/archive/html/emacs-devel/2020-01/msg00843.html
https://lists.gnu.org/archive/html/emacs-devel/2020-02/msg00042.html
https://lists.gnu.org/archive/html/emacs-devel/2020-02/msg00282.html
* etc/NEWS: Announce that battery-upower is enabled by default.
* lisp/battery.el (battery-upower-device): Accept both battery and
line power device names, or a list thereof (bug#39491).
(battery-upower-line-power-device): Remove user option; superseded
by battery-upower-device.
(battery-upower-subscribe): New user option.
(battery-status-function): Check whether a UPower service is
provided without activating it.
(display-battery-mode): Subscribe to UPower signals when using
battery-upower.
(battery-upower): Merge data from multiple power sources. Calculate
terse battery status %b based on average battery load percentage
rather than coarse and often missing BatteryLevel (bug#39491). Add
support for average temperature %d.
(battery-upower-dbus-service)
(battery-upower-dbus-interface)
(battery-upower-dbus-path)
(battery-upower-dbus-device-interface)
(battery-upower-dbus-device-path)
(battery-upower-device-all-properties): Rename to...
(battery-upower-service)
(battery-upower-interface)
(battery-upower-path)
(battery-upower-device-interface)
(battery-upower-device-path)
(battery--upower-device-properties): ...these, respectively.
(battery-upower-device-list): Rename to...
(battery--upower-devices) ...this. Return a flat list of device
names determined by battery-upower-device.
(battery-upower-types, battery-upower-states)
(battery-upower-device-property, battery-upower-device-autodetect):
Remove.
(battery--upower-signals): New variable.
(battery--upower-signal-handler, battery--upower-props-changed)
(battery--upower-unsubscribe, battery--upower-subsribe)
(battery--upower-state): New functions.
* test/lisp/battery-tests.el (battery-upower-state)
(battery-upower-state-unknown): New tests.
Diffstat (limited to 'lisp/battery.el')
-rw-r--r-- | lisp/battery.el | 294 |
1 files changed, 183 insertions, 111 deletions
diff --git a/lisp/battery.el b/lisp/battery.el index f6f70b2f16d..e568ab52460 100644 --- a/lisp/battery.el +++ b/lisp/battery.el @@ -44,21 +44,40 @@ :group 'hardware) (defcustom battery-upower-device nil - "UPower device of the `:battery' type. -Use `battery-upower-device-list' to list all available UPower devices. -If set to nil, then autodetect `:battery' device." + "Preferred UPower device name(s). +When `battery-status-function' is set to `battery-upower', this +user option specifies which power sources to query for status +information and merge into a single report. + +When nil (the default), `battery-upower' queries all present +battery and line power devices as determined by the UPower +EnumerateDevices method. A string or a nonempty list of strings +names particular devices to query instead. UPower battery and +line power device names typically follow the patterns +\"battery_BATN\" and \"line_power_ACN\", respectively, with N +starting at 0 when present. Device names should not include the +leading D-Bus path \"/org/freedesktop/UPower/devices/\"." :version "28.1" - :type '(choice string (const :tag "Autodetect" nil))) - -(defcustom battery-upower-line-power-device nil - "UPower device of the `:line-power' type. -Use `battery-upower-device-list' to list all available UPower devices. -If set to nil, then autodetect `:battery' device." + :type '(choice (const :tag "Autodetect all devices" nil) + (string :tag "Device") + (repeat :tag "Devices" string))) + +(defcustom battery-upower-subscribe t + "Whether to subscribe to UPower device change signals. +When nil, battery status information is polled every +`battery-update-interval' seconds. When non-nil (the default), +the battery status is also updated whenever a power source is +added or removed, or when the system starts or stops running on +battery power. + +This only takes effect when `battery-status-function' is set to +`battery-upower' before enabling `display-battery-mode'." :version "28.1" - :type '(choice string (const :tag "Autodetect" nil))) + :type 'boolean) -(defconst battery-upower-dbus-service "org.freedesktop.UPower" - "Well-known UPower service name for the D-Bus system.") +(defconst battery-upower-service "org.freedesktop.UPower" + "Well-known name of the UPower D-Bus service. +See URL `https://upower.freedesktop.org/docs/ref-dbus.html'.") (defun battery--files (dir) "Return a list of absolute file names in DIR or nil on error. @@ -74,7 +93,7 @@ Value does not include \".\" or \"..\"." (nreverse dirs))) (defcustom battery-status-function - (cond ((dbus-ping :system battery-upower-dbus-service) + (cond ((member battery-upower-service (dbus-list-activatable-names)) #'battery-upower) ((and (eq system-type 'gnu/linux) (battery--find-linux-sysfs-batteries)) @@ -219,11 +238,15 @@ seconds." (setq battery-mode-line-string "") (or global-mode-string (setq global-mode-string '(""))) (and battery-update-timer (cancel-timer battery-update-timer)) + (battery--upower-unsubscribe) (if (and battery-status-function battery-mode-line-format) (if (not display-battery-mode) (setq global-mode-string (delq 'battery-mode-line-string global-mode-string)) (add-to-list 'global-mode-string 'battery-mode-line-string t) + (and (eq battery-status-function #'battery-upower) + battery-upower-subscribe + (battery--upower-subsribe)) (setq battery-update-timer (run-at-time nil battery-update-interval #'battery-update-handler)) (battery-update)) @@ -577,123 +600,172 @@ The following %-sequences are provided: (_ "N/A")))))) -;;; `upowerd' interface. -(defconst battery-upower-dbus-interface "org.freedesktop.UPower" - "The interface to UPower. -See URL `https://upower.freedesktop.org/docs/'.") +;;; UPower interface. + +(defconst battery-upower-interface "org.freedesktop.UPower" + "Name of the UPower D-Bus interface. +See URL `https://upower.freedesktop.org/docs/UPower.html'.") -(defconst battery-upower-dbus-path "/org/freedesktop/UPower" - "D-Bus path to talk to UPower service.") +(defconst battery-upower-path "/org/freedesktop/UPower" + "D-Bus object providing `battery-upower-interface'.") -(defconst battery-upower-dbus-device-interface - (concat battery-upower-dbus-interface ".Device") - "The Device interface of the UPower. +(defconst battery-upower-device-interface "org.freedesktop.UPower.Device" + "Name of the UPower Device D-Bus interface. See URL `https://upower.freedesktop.org/docs/Device.html'.") -(defconst battery-upower-dbus-device-path - (concat battery-upower-dbus-path "/devices") - "D-Bus path to talk to devices part of the UPower service.") - -(defconst battery-upower-types - '((0 . :unknown) (1 . :line-power) (2 . :battery) - (3 . :ups) (4 . :monitor) (5 . :mouse) - (6 . :keyboard) (7 . :pda) (8 . :phone)) - "Type of the device.") - -(defconst battery-upower-states - '((0 . "unknown") (1 . "charging") (2 . "discharging") - (3 . "empty") (4 . "fully-charged") (5 . "pending-charge") - (6 . "pending-discharge")) - "Alist of battery power states. -Only valid for `:battery' devices.") - -(defun battery-upower-device-property (device property) - "Get value of the single PROPERTY for the UPower DEVICE." - (dbus-get-property - :system battery-upower-dbus-service - (expand-file-name device battery-upower-dbus-device-path) - battery-upower-dbus-device-interface - property)) - -(defun battery-upower-device-all-properties (device) +(defconst battery-upower-device-path "/org/freedesktop/UPower/devices" + "D-Bus object providing `battery-upower-device-interface'.") + +(defvar battery--upower-signals nil + "Handles for UPower signal subscriptions.") + +(defun battery--upower-signal-handler (&rest _) + "Update battery status on receiving a UPower D-Bus signal." + (timer-event-handler battery-update-timer)) + +(defun battery--upower-props-changed (_interface changed _invalidated) + "Update status when system starts/stops running on battery. +Intended as a UPower PropertiesChanged signal handler." + (when (assoc "OnBattery" changed) + (battery--upower-signal-handler))) + +(defun battery--upower-unsubscribe () + "Unsubscribe from UPower device change signals." + (mapc #'dbus-unregister-object battery--upower-signals) + (setq battery--upower-signals ())) + +(defun battery--upower-subsribe () + "Subscribe to UPower device change signals." + (push (dbus-register-signal :system battery-upower-service + battery-upower-path + dbus-interface-properties + "PropertiesChanged" + #'battery--upower-props-changed) + battery--upower-signals) + (dolist (method '("DeviceAdded" "DeviceRemoved")) + (push (dbus-register-signal :system battery-upower-service + battery-upower-path + battery-upower-interface + method #'battery--upower-signal-handler) + battery--upower-signals))) + +(defun battery--upower-device-properties (device) "Return value for all available properties for the UPower DEVICE." (dbus-get-all-properties - :system battery-upower-dbus-service - (expand-file-name device battery-upower-dbus-device-path) - battery-upower-dbus-device-interface)) - -(defun battery-upower-device-list () - "Return list of all available UPower devices. -Each element is the cons cell in form: (DEVICE . DEVICE-TYPE)." - (mapcar (lambda (device-path) - (let* ((device (file-relative-name - device-path battery-upower-dbus-device-path)) - (type-num (battery-upower-device-property device "Type"))) - (cons device (or (cdr (assq type-num battery-upower-types)) - :unknown)))) - (dbus-call-method :system battery-upower-dbus-service - battery-upower-dbus-path - battery-upower-dbus-interface - "EnumerateDevices"))) - -(defun battery-upower-device-autodetect (device-type) - "Return first matching UPower device of DEVICE-TYPE." - (car (rassq device-type (battery-upower-device-list)))) + :system battery-upower-service + (expand-file-name device battery-upower-device-path) + battery-upower-device-interface)) + +(defun battery--upower-devices () + "List all UPower devices according to `battery-upower-device'." + (cond ((stringp battery-upower-device) + (list battery-upower-device)) + (battery-upower-device) + ((dbus-call-method :system battery-upower-service + battery-upower-path + battery-upower-interface + "EnumerateDevices")))) + +(defun battery--upower-state (props state) + "Merge the UPower battery state in PROPS with STATE. +This is an extension of the UPower DisplayDevice algorithm for +merging multiple battery states into one. PROPS is an alist of +battery properties from `battery-upower-device-interface', and +STATE is a symbol representing the state to merge with." + ;; Map UPower enum into our printable symbols. + (let* ((new (pcase (cdr (assoc "State" props)) + (1 'charging) + (2 'discharging) + (3 'empty) + (4 'fully-charged) + (5 'pending-charge) + (6 'pending-discharge))) + ;; Unknown state represented by nil. + (either (delq nil (list new state)))) + ;; Earlier states override later ones. + (car (cond ((memq 'charging either)) + ((memq 'discharging either)) + ((memq 'pending-charge either)) + ((memq 'pending-discharge either)) + ;; Only options left are full or empty, + ;; but if they conflict return nil. + ((null (cdr either)) either) + ((apply #'eq either) either))))) (defun battery-upower () - "Get battery status from dbus Upower interface. -This function works only in systems with `upowerd' daemon -running. + "Get battery status from UPower D-Bus interface. +This function works only in systems that provide a UPower D-Bus +service. The following %-sequences are provided: %c Current capacity (mWh) -%p Battery load percentage -%r Current rate +%r Current rate of charge or discharge +%L AC line status (verbose) %B Battery status (verbose) %b Battery status: empty means high, `-' means low, `!' means critical, and `+' means charging -%L AC line status (verbose) +%d Temperature (in degrees Celsius) +%p Battery load percentage %s Remaining time (to charge or discharge) in seconds %m Remaining time (to charge or discharge) in minutes %h Remaining time (to charge or discharge) in hours %t Remaining time (to charge or discharge) in the form `h:min'" - (let* ((bat-device (or battery-upower-device - (battery-upower-device-autodetect :battery))) - (bat-props (when bat-device - (battery-upower-device-all-properties bat-device))) - (percents (cdr (assoc "Percentage" bat-props))) - (time-to-empty (cdr (assoc "TimeToEmpty" bat-props))) - (time-to-full (cdr (assoc "TimeToFull" bat-props))) - (state (cdr (assoc "State" bat-props))) - (level (cdr (assoc "BatteryLevel" bat-props))) - (energy (cdr (assoc "Energy" bat-props))) - (energy-rate (cdr (assoc "EnergyRate" bat-props))) - (lp-device (or battery-upower-line-power-device - (battery-upower-device-autodetect :line-power))) - (online-p (when lp-device - (battery-upower-device-property lp-device "Online"))) - (seconds (if online-p time-to-full time-to-empty)) - (minutes (when seconds (/ seconds 60))) - (hours (when minutes (/ minutes 60))) - (remaining-time (when hours - (format "%d:%02d" hours (mod minutes 60))))) - (list (cons ?c (if energy (number-to-string (round (* 1000 energy))) "N/A")) - (cons ?p (if percents (number-to-string (round percents)) "N/A")) - (cons ?r (if energy-rate - (concat (number-to-string energy-rate) " W") + (let ((count 0) props type line-status state load temperature + secs mins hrs total-energy total-rate total-tte total-ttf) + ;; Merge information from all available or specified UPower + ;; devices like other `battery-status-function's. + (dolist (device (battery--upower-devices)) + (setq props (battery--upower-device-properties device)) + (setq type (cdr (assoc "Type" props))) + (cond + ((and (eq type 1) (not (eq line-status 'online))) + ;; It's a line power device: `online' if currently providing + ;; power, any other non-nil value if simply present. + (setq line-status (if (cdr (assoc "Online" props)) 'online t))) + ((and (eq type 2) (cdr (assoc "IsPresent" props))) + ;; It's a battery. + (setq count (1+ count)) + (setq state (battery--upower-state props state)) + (let ((energy (cdr (assoc "Energy" props))) + (rate (cdr (assoc "EnergyRate" props))) + (percent (cdr (assoc "Percentage" props))) + (temp (cdr (assoc "Temperature" props))) + (tte (cdr (assoc "TimeToEmpty" props))) + (ttf (cdr (assoc "TimeToFull" props)))) + (when energy (setq total-energy (+ (or total-energy 0) energy))) + (when rate (setq total-rate (+ (or total-rate 0) rate))) + (when percent (setq load (+ (or load 0) percent))) + (when temp (setq temperature (+ (or temperature 0) temp))) + (when tte (setq total-tte (+ (or total-tte 0) tte))) + (when ttf (setq total-ttf (+ (or total-ttf 0) ttf))))))) + (when (> count 1) + ;; Averages over multiple batteries. + (when load (setq load (/ load count))) + (when temperature (setq temperature (/ temperature count)))) + (when (setq secs (if (eq line-status 'online) total-ttf total-tte)) + (setq mins (/ secs 60)) + (setq hrs (/ secs 3600))) + (list (cons ?c (if total-energy + (format "%.0f" (* total-energy 1000)) "N/A")) - (cons ?B (if state - (cdr (assq state battery-upower-states)) - "unknown")) - (cons ?b (cond ((= level 3) "-") - ((= level 4) "!") - (online-p "+") - (t ""))) - (cons ?L (if online-p "on-line" (if lp-device "off-line" "unknown"))) - (cons ?s (if seconds (number-to-string seconds) "N/A")) - (cons ?m (if minutes (number-to-string minutes) "N/A")) - (cons ?h (if hours (number-to-string hours) "N/A")) - (cons ?t (or remaining-time "N/A"))))) + (cons ?r (if total-rate (format "%.1f W" total-rate) "N/A")) + (cons ?L (cond ((eq line-status 'online) "on-line") + (line-status "off-line") + ("N/A"))) + (cons ?B (format "%s" (or state 'unknown))) + (cons ?b (cond ((eq state 'charging) "+") + ((and load (< load battery-load-critical)) "!") + ((and load (< load battery-load-low)) "-") + (""))) + ;; Zero usually means unknown. + (cons ?d (if (and temperature (/= temperature 0)) + (format "%.0f" temperature) + "N/A")) + (cons ?p (if load (format "%.0f" load) "N/A")) + (cons ?s (if secs (number-to-string secs) "N/A")) + (cons ?m (if mins (number-to-string mins) "N/A")) + (cons ?h (if hrs (number-to-string hrs) "N/A")) + (cons ?t (if hrs (format "%d:%02d" hrs (% mins 60)) "N/A"))))) ;;; `apm' interface for BSD. |