aboutsummaryrefslogtreecommitdiff
path: root/modules/host-environment.el
blob: 0afb39cbfc5720e5b32115d9c6724ee3fdcbbc8c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
;;; host-environment.el --- Host Environment Convenience Functions -*- lexical-binding: t; coding: utf-8; -*-
;; author Craig Jennings <c@cjennings.net>

;;; Commentary:
;;
;; Layer: 1 (Foundation).
;; Category: F/L.
;; Load shape: eager.
;; Eager reason: host predicates (laptop-p, battery presence) used by other
;;   foundation/UI modules deciding behavior at startup.
;; Top-level side effects: none.
;; Runtime requires: none (battery loaded on demand; declared via
;;   `declare-function').
;; Direct test load: yes (pure predicates; batch-safe).
;;
;; Convenience functions to report about the host environment
;;
;;; Code:

;; for the byte-compiler/linter
(declare-function battery-format "battery" (format data))
(defvar battery-status-function)



(defun env--battery-status-char-indicates-battery-p (status)
  "Return non-nil if STATUS indicates a real battery is present.
STATUS is the value returned by `battery-format' with the \"%B\" spec.
A real battery reports \"!\" (critical), \"+\" (charging), or \"-\"
(discharging).  Desktops report \"N/A\", \"unknown\", or empty, which
all mean no battery."
  (and (stringp status)
	   (member status '("!" "+" "-"))
	   t))

(defun env--power-supply-has-battery-p (power-supply-dir)
  "Return non-nil if POWER-SUPPLY-DIR contains a BAT* entry.
Canonical Linux check.  A laptop exposes directories like
/sys/class/power_supply/BAT0; a desktop exposes only AC adapters
and USB-C power entries."
  (and (file-directory-p power-supply-dir)
	   (file-expand-wildcards (expand-file-name "BAT*" power-supply-dir))
	   t))

(defun env-laptop-p ()
  "Non-nil if the host has a battery.
On Linux, checks /sys/class/power_supply for BAT* entries, which is the
canonical signal.  On other platforms, falls back to `battery-format'
\"%B\" and checks for a live battery status char."
  (cond
   ((eq system-type 'gnu/linux)
	(env--power-supply-has-battery-p "/sys/class/power_supply"))
   ((and (require 'battery nil 'noerror)
		 battery-status-function)
	(env--battery-status-char-indicates-battery-p
	 (battery-format "%B" (funcall battery-status-function))))))

(defun env-desktop-p ()
  "Return t if host is a desktop (no battery), nil if it has one."
  (when (not (env-laptop-p))
	t))

(defun env-linux-p ()
  "Return t if host system is GNU/Linux."
  (eq system-type 'gnu/linux))

(defun env-bsd-p ()
  "Return t if host system is FreeBSD."
  (eq system-type 'berkeley-unix))

(defun env-macos-p ()
  "Return non-nil if running on macOS (Darwin)."
  (eq system-type 'darwin))

(defun env-windows-p ()
  "Return t if host system is Windows."
  (memq system-type '(cygwin windows-nt ms-dos)))

(defun env-x-p ()
  "Return t if the frame uses an X display, including XWayland.
Compare `env-x11-p', which is t only in a real X11 session and nil under
Wayland/XWayland.  Uses symbol comparison to match `env-x11-p' style."
  (eq (window-system) 'x))

(defun env-x11-p ()
  "Return t in a real X11 session: an X display with no WAYLAND_DISPLAY.
Compare `env-x-p', which is also t under XWayland (X display, Wayland set)."
  (and (eq (window-system) 'x)
       (not (getenv "WAYLAND_DISPLAY"))))

(defun env-wayland-p ()
  "Return t if running under Wayland.
Checks WAYLAND_DISPLAY env var, which is set by Wayland compositors.
This returns t even if Emacs is running through XWayland."
  (and (getenv "WAYLAND_DISPLAY") t))

(defun env-terminal-p ()
  "Return t if running in a terminal."
  (not (display-graphic-p)))

(defun env-gui-p ()
  "Return t if running in graphical environment."
  (display-graphic-p))

;; ------------------------------- Timezone Info -------------------------------

(defun cj/match-localtime-to-zoneinfo ()
  "Detect system timezone by comparing /etc/localtime with zoneinfo files.
This replicates the shell command:
find /usr/share/zoneinfo -type f ! -name `posixrules' \\
  -exec cmp -s {} /etc/localtime \\;
  -print | sed -e \='s@.*/zoneinfo/@@\=' | head -n1"
  (when (and (file-exists-p "/etc/localtime")
			 (file-directory-p "/usr/share/zoneinfo"))
	(let ((localtime-content
		   (with-temp-buffer
			 (insert-file-contents-literally "/etc/localtime")
			 (buffer-string)))
		  (result nil))
	  (catch 'found
		(dolist (file (directory-files-recursively
					   "/usr/share/zoneinfo"
					   ".*"
					   nil
					   (lambda (name)
						 (not (string-match-p "posixrules" name)))))
		  (when (and (file-regular-p file)
					 (not (string-match-p "/posixrules$" file)))
			(let ((file-content
				   (with-temp-buffer
					 (insert-file-contents-literally file)
					 (buffer-string))))
			  (when (string= localtime-content file-content)
				(setq result (replace-regexp-in-string
							  "^.*/zoneinfo/" "" file))
				(throw 'found result)))))
		result))))

(defun cj/detect-system-timezone ()
  "Detect the system timezone in IANA format (e.g., `America/Los_Angeles').
Tries multiple methods in order of reliability:
1. File comparison of /etc/localtime with zoneinfo database
2. Environment variable TZ
3. /etc/timezone file contents
4. /etc/localtime symlink target"
  (or
   ;; Compare file contents (reliable on Arch/modern systems)
   (cj/match-localtime-to-zoneinfo)

   ;; Environment variable (most explicit if set)
   (getenv "TZ")

   ;; Read /etc/timezone (Debian/Ubuntu)
   (when (file-exists-p "/etc/timezone")
	 (with-temp-buffer
	   (insert-file-contents "/etc/timezone")
	   (string-trim (buffer-string))))

   ;; Method 4: Parse symlink (fallback for older systems)
   (when (file-symlink-p "/etc/localtime")
	 (let ((target (file-truename "/etc/localtime")))
	   (when (string-match ".*/zoneinfo/\\(.+\\)" target)
		 (match-string 1 target))))

   ;; Default to nil if detection fails
   nil))

(provide 'host-environment)
;;; host-environment.el ends here.