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();