bring power-monitor server side to fix mac battery levels

Signed-off-by: si458 <simonsmith5521@gmail.com>

si458 committed May 13, 2024 at 21:47 UTC b71b4d04e67175492f608c3b69eb5b2ce2b22804
1 file changed +298
agents/modules_meshcore/power-monitor.js new
+298
@@ -0,0 +1,298 @@
1 +/*
2 +Copyright 2018-2020 Intel Corporation
3 +
4 +Licensed under the Apache License, Version 2.0 (the "License");
5 +you may not use this file except in compliance with the License.
6 +You may obtain a copy of the License at
7 +
8 + http://www.apache.org/licenses/LICENSE-2.0
9 +
10 +Unless required by applicable law or agreed to in writing, software
11 +distributed under the License is distributed on an "AS IS" BASIS,
12 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 +See the License for the specific language governing permissions and
14 +limitations under the License.
15 +*/
16 +
17 +var WM_SYSCOMMAND = 0x0112;
18 +var SC_MONITORPOWER = 0xF170;
19 +var HWND_BROADCAST = 0xffff;
20 +var ES_DISPLAY_REQUIRED = 0x00000002;
21 +
22 +function powerMonitor()
23 +{
24 + this._ObjectID = 'power-monitor';
25 + require('events').EventEmitter.call(this, true)
26 + .createEvent('changed')
27 + .createEvent('sx')
28 + .createEvent('batteryLevel')
29 + .createEvent('acdc')
30 + .createEvent('display');
31 +
32 + this._ACState = 1;
33 + this._BatteryLevel = -1;
34 +
35 + if (process.platform == 'win32')
36 + {
37 + // These must be registered BEFORE newListener is hooked up
38 + this.on('batteryLevel', function (level) { this._BatteryLevel = level; });
39 + this.on('acdc', function (m) { this._ACState = (m == 'AC' ? 1 : 0); });
40 + }
41 +
42 + this.on('newListener', function (name, callback)
43 + {
44 + if (name == 'acdc') { callback.call(this, this._ACState == 1 ? 'AC' : 'BATTERY'); }
45 + if (name == 'batteryLevel') { if (this._BatteryLevel >= 0) { callback.call(this, this._BatteryLevel); } }
46 + });
47 +
48 + this._i = setImmediate(function (self)
49 + {
50 + require('user-sessions'); // This is needed because this is where the Windows Messages are processed for these events
51 + delete self._i;
52 + }, this);
53 +
54 + if (process.platform == 'linux')
55 + {
56 + this._ACPath = null;
57 + this._BatteryPath = [];
58 +
59 + var devices = require('fs').readdirSync('/sys/class/power_supply');
60 + for (var i in devices)
61 + {
62 + if (require('fs').readFileSync('/sys/class/power_supply/' + devices[i] + '/type').toString().trim() == 'Mains')
63 + {
64 + this._ACPath = '/sys/class/power_supply/' + devices[i] + '/';
65 + break;
66 + }
67 + }
68 + for (var i in devices)
69 + {
70 + if (require('fs').readFileSync('/sys/class/power_supply/' + devices[i] + '/type').toString().trim() == 'Battery')
71 + {
72 + this._BatteryPath.push('/sys/class/power_supply/' + devices[i] + '/');
73 + }
74 + }
75 + if (this._ACPath != null)
76 + {
77 + this._ACState = parseInt(require('fs').readFileSync(this._ACPath + 'online').toString().trim());
78 + }
79 + if (this._BatteryPath.length > 0)
80 + {
81 + this._getBatteryLevel = function _getBatteryLevel()
82 + {
83 + var sum = 0;
84 + var i;
85 + for (i in this._BatteryPath)
86 + {
87 + sum += parseInt(require('fs').readFileSync(this._BatteryPath[i] + 'capacity').toString().trim());
88 + }
89 + sum = Math.floor(sum / this._BatteryPath.length);
90 + return (sum);
91 + }
92 + this._BatteryLevel = this._getBatteryLevel();
93 +
94 + // Since Battery Levels are not propagated with ACPI, we need to periodically check the battery level
95 + this._BatteryLevelCheck = function _BatteryLevelCheck()
96 + {
97 + var val = this._getBatteryLevel();
98 + if (val != this._BatteryLevel)
99 + {
100 + this._BatteryLevel = val;
101 + this.emit('batteryLevel', val);
102 + }
103 + };
104 + this._BattCheckInterval = setInterval(function (self)
105 + {
106 + self._BatteryLevelCheck.call(self);
107 + }, 300000, this);
108 + }
109 + this._acpiSink = function _acpiSink(acpiEvent)
110 + {
111 + if (acpiEvent.name == 'ac_adapter')
112 + {
113 + _acpiSink.self._ACState = acpiEvent.value;
114 + _acpiSink.self.emit('acdc', acpiEvent.value == 1 ? 'AC' : 'BATTERY');
115 + _acpiSink.self._BatteryLevelCheck();
116 + }
117 + };
118 + this._acpiSink.self = this;
119 + require('linux-acpi').on('acpi', this._acpiSink);
120 + }
121 + if (process.platform == 'darwin')
122 + {
123 + Object.defineProperty(this, "_caffeinate", {
124 + value: (function ()
125 + {
126 + var child = require('child_process').execFile('/bin/sh', ['sh']);
127 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
128 + child.stdin.write('whereis caffeinate\nexit\n');
129 + child.waitExit();
130 + return (child.stdout.str.trim());
131 + })()
132 + });
133 + this._getBatteryLevel = function _getBatteryLevel()
134 + {
135 + var child = require('child_process').execFile('/bin/sh', ['sh']);
136 + child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
137 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
138 + child.stdin.write("pmset -g batt | tr '\\n' '`' | awk -F'`' '");
139 + child.stdin.write('{');
140 + child.stdin.write(' power=split($1,pwr,"AC")>1?"1":"0";');
141 + child.stdin.write(' split($2, batt, " ");');
142 + child.stdin.write(' split(batt[2],chg,"%");');
143 + child.stdin.write(' printf "{\\"ac\\": %s,\\"level\\": %s}",power, chg[1]; ');
144 + child.stdin.write("}'\nexit\n");
145 + child.waitExit();
146 + try {
147 + var info = JSON.parse(child.stdout.str.trim());
148 + return (info);
149 + } catch (e) {
150 + child = require('child_process').execFile('/bin/sh', ['sh']);
151 + child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
152 + child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
153 + child.stdin.write("pmset -g batt | tr '\\n' '`' | awk -F'`' '");
154 + child.stdin.write('{');
155 + child.stdin.write(' power=split($1,pwr,"AC")>1?"1":"0";');
156 + child.stdin.write(' split($2, batt, " ");');
157 + child.stdin.write(' split(batt[3],chg,"%");');
158 + child.stdin.write(' printf "{\\"ac\\": %s,\\"level\\": %s}",power, chg[1]; ');
159 + child.stdin.write("}'\nexit\n");
160 + child.waitExit();
161 + try {
162 + var info = JSON.parse(child.stdout.str.trim());
163 + return (info);
164 + } catch (e) {
165 + return ({ ac: 1, level: -1 });
166 + }
167 + }
168 + };
169 + this._batteryLevelCheck = function _batteryLevelCheck()
170 + {
171 + var newLevel = this._getBatteryLevel();
172 + if (newLevel.ac != this._ACState)
173 + {
174 + this._ACState = newLevel.ac;
175 + this.emit('acdc', this._ACState == 1 ? 'AC' : 'BATTERY');
176 + }
177 + if (newLevel.level != this._BatteryLevel)
178 + {
179 + this._BatteryLevel = newLevel.level;
180 + this.emit('batteryLevel', this._BatteryLevel);
181 + }
182 + };
183 + var tmp = this._getBatteryLevel();
184 + this._ACState = tmp.ac;
185 + this._BatteryLevel = tmp.level;
186 +
187 + if (this._BatteryLevel >= 0)
188 + {
189 + this._BattCheckInterval = setInterval(function (self)
190 + {
191 + self._batteryLevelCheck.call(self);
192 + }, 300000, this);
193 + }
194 + }
195 + this.sleepDisplay = function sleepDispay(force)
196 + {
197 + var promise = require('promise');
198 + p = new promise(function (res, rej) { this._res = res; this._rej = rej; });
199 +
200 + switch (process.platform)
201 + {
202 + case 'win32':
203 + if (require('user-sessions').getProcessOwnerName(process.pid).tsid == 0)
204 + {
205 + // We are running as LocalSystem, so we have to find a user session for this to work
206 + var options = { launch: { module: 'power-monitor', method: 'sleepDisplay', args: [] } };
207 + try
208 + {
209 + options.user = require('user-sessions').getUsername(require('user-sessions').consoleUid());
210 + }
211 + catch (ee)
212 + {
213 + p._rej('No users logged in');
214 + return (p);
215 + }
216 + p.child = require('child-container').create(options);
217 + p.child.promise = p;
218 + p.child.on('exit', function () { this.promise._res(); });
219 + }
220 + else
221 + {
222 + if (require('child-container').child) { require('win-console').hide(); }
223 + var GM = require('_GenericMarshal');
224 + var user32 = GM.CreateNativeProxy('User32.dll');
225 + user32.CreateMethod('SendMessageA');
226 + user32.SendMessageA(HWND_BROADCAST, WM_SYSCOMMAND, SC_MONITORPOWER, 2);
227 + p._res();
228 + if (require('child-container').child) { process._exit(); }
229 + }
230 + break;
231 + case 'darwin':
232 + p.child = require('child_process').execFile('/bin/sh', ['sh']);
233 + p.child.promise = p;
234 + p.child.stderr.on('data', function () { });
235 + p.child.stdout.on('data', function () { });
236 + p.child.on('exit', function () { this.promise._res(); });
237 + p.child.stdin.write('pmset displaysleepnow\nexit\n');
238 + break;
239 + default:
240 + p._rej('Not Supported');
241 + break;
242 + }
243 + return (p);
244 + };
245 + this.wakeDisplay = function wakeDisplay()
246 + {
247 + var promise = require('promise');
248 + p = new promise(function (res, rej) { this._res = res; this._rej = rej; });
249 + switch(process.platform)
250 + {
251 + case 'darwin':
252 + if (this._caffeinate)
253 + {
254 + p.child = require('child_process').execFile(this._caffeinate, ['caffeinate', '-u', '-t 2']);
255 + p.child.stdout.on('data', function () { });
256 + p.child.stderr.on('data', function () { });
257 + p.child.on('exit', function (code) { this.promise._res(); });
258 + p.child.promise = p;
259 + }
260 + break;
261 + case 'win32':
262 + if (require('user-sessions').getProcessOwnerName(process.pid).tsid == 0)
263 + {
264 + // We are running as LocalSystem, so we have to find a user session for this to work
265 + var options = { launch: { module: 'power-monitor', method: 'wakeDisplay', args: [] } };
266 + try
267 + {
268 + options.user = require('user-sessions').getUsername(require('user-sessions').consoleUid());
269 + }
270 + catch (ee)
271 + {
272 + p._rej('No users logged in');
273 + return (p);
274 + }
275 + p.child = require('child-container').create(options);
276 + p.child.promise = p;
277 + p.child.on('exit', function () { this.promise._res(); });
278 + }
279 + else
280 + {
281 + if (require('child-container').child) { require('win-console').hide(); }
282 + var GM = require('_GenericMarshal');
283 + var kernel32 = GM.CreateNativeProxy('Kernel32.dll');
284 + kernel32.CreateMethod('SetThreadExecutionState');
285 + kernel32.SetThreadExecutionState(ES_DISPLAY_REQUIRED);
286 + p._res();
287 + if (require('child-container').child) { process._exit(); }
288 + }
289 + break;
290 + default:
291 + p._res();
292 + break;
293 + }
294 + return (p);
295 + };
296 +}
297 +
298 +module.exports = new powerMonitor();