import BaseModule from "../lib/base-module"; import {IBlockConfig, IBlockOutput} from "../models/config-types"; import {IModuleDataFunction} from "../models/module-block"; import {ChildProcess, spawn} from "child_process"; import {clearTimeout, setTimeout} from "timers"; import xtend = require("xtend"); const icons = { level_0: "\uF244", level_100: "\uF240", level_25: "\uF243", level_50: "\uF242", level_75: "\uF241", plug: "\uF1E6", }; export = (dataCallback: IModuleDataFunction, config: IBlockConfig) => { return new BatteryModule(dataCallback, config); }; class BatteryModule extends BaseModule { private upowerDataGetRunning = false; private upowerMonitorRunning = false; private upowerMonitorProcess: ChildProcess; private upowerMonitorTimeout: NodeJS.Timer; constructor(dataCallback: IModuleDataFunction, config: IBlockConfig) { super(dataCallback, config); this.config.params = xtend({ battery: "BAT0", chargedText: "F", chargingText: "C", dischargingText: "D", fontAwesome: false, urgentLevel: 10, }, this.config.params); } public start() { super.start(); this.startUpowerMonitor(); } public stop() { super.stop(); this.upowerMonitorProcess.kill(); } protected onTick(): void { this.runUPower(); } private output(data: IUPower): void { let fullText: string = `${this.getIcon(data.state, data.percentage)} ${data.percentage}%`; if (data.state === "discharging") { fullText += ` (${data.timeDischarging})`; } else if (data.state === "charging") { fullText += ` (${data.timeCharging})`; } let outputData: IBlockOutput = { color: percentageToColor(data.percentage), full_text: fullText, short_text: data.percentage.toString(), }; if (data.percentage <= this.config.params.urgentLevel) { outputData.urgent = true; } this.dataCallback(outputData); } private getIcon(state: string, percentage: number): string { if (this.config.params.fontAwesome) { if (state === "fully-charged") { return icons.plug; } else if (percentage > 90) { return icons.level_100; } else if (percentage > 75) { return icons.level_75; } else if (percentage > 50) { return icons.level_50; } else if (percentage > 25) { return icons.level_25; } else { return icons.level_0; } } else { switch (state) { case "discharging": return this.config.params.dischargingText; case "charging": return this.config.params.chargingText; case "fully-charged": return this.config.params.chargedText; default: return ""; } } } private startUpowerMonitor(): void { if (this.upowerMonitorRunning) { return; } this.upowerMonitorRunning = true; this.upowerMonitorProcess = spawn("upower", [ "--monitor", ]); this.upowerMonitorProcess.stdout.on("data", () => { clearTimeout(this.upowerMonitorTimeout); this.upowerMonitorTimeout = setTimeout(() => this.onTick(), 100); }); this.upowerMonitorProcess.on("error", () => { // TODO Need to implement a way for modules to log non fatal errors }); } private runUPower(): void { if (this.upowerDataGetRunning) { return; } this.upowerDataGetRunning = true; let rawOutput: string = ""; let upowerProcess: ChildProcess = spawn("upower", [ "-i", `/org/freedesktop/UPower/devices/battery_${this.config.params.battery}`, ]); upowerProcess.stdout.on("data", (data: Buffer) => { rawOutput += data.toString(); }); upowerProcess.on("exit", (exitCode: number) => { if (exitCode === 0) { this.output(this.parseUPowerOutput(rawOutput)); this.upowerDataGetRunning = false; } }); upowerProcess.on("error", (error: Error) => { this.dataCallback({ full_text: error.message, }); }); } private parseUPowerOutput(rawOutput: string): IUPower { let state: string = ""; let percentage: number = 0; let timeCharging: string = ""; let timeDischarging: string = ""; let lines = rawOutput.split(/\n/g); let outputLength = lines.length; let line: string; let matches: Array; let option: string; let value: string; for (let i = 0; i < outputLength; i++) { line = lines[i]; matches = line.match(/^\s+([a-z\- ]+):\s+(.+)$/); if (!matches || matches.length !== 3) { continue; } option = matches[1]; value = matches[2]; // tslint:disable-next-line:switch-default switch (option) { case "state": state = value; break; case "percentage": percentage = parseInt(value, 10); break; case "time to full": timeCharging = value; break; case "time to empty": timeDischarging = value; break; } } return { state, percentage, timeCharging, timeDischarging, }; } } function percentageToColor(percentage: number): string { let blue: string; let green: string; let red: string; if (percentage > 50) { red = Math.round((100 - percentage) * 2.55 * 2).toString(16); red = "00".substring(red.length) + red; green = "FF"; blue = "00"; } else { red = "FF"; green = Math.round(percentage * 2.55 * 2).toString(16); green = "00".substring(green.length) + green; blue = "00"; } return "#" + red + green + blue; } interface IUPower { state: string; percentage: number; timeCharging: string; timeDischarging: string; }