diff options
Diffstat (limited to 'src/detection/battery/battery_android.c')
| -rw-r--r-- | src/detection/battery/battery_android.c | 160 |
1 files changed, 160 insertions, 0 deletions
diff --git a/src/detection/battery/battery_android.c b/src/detection/battery/battery_android.c new file mode 100644 index 0000000..d76c4ad --- /dev/null +++ b/src/detection/battery/battery_android.c @@ -0,0 +1,160 @@ +#include "fastfetch.h" +#include "battery.h" +#include "common/strutil.h" +#include "common/processing.h" +#include "common/properties.h" + +#define FF_TERMUX_API_PATH FASTFETCH_TARGET_DIR_ROOT "/libexec/termux-api" +#define FF_TERMUX_API_PARAM "BatteryStatus" + +static inline void wrapYyjsonFree(yyjson_doc** doc) { + assert(doc); + if (*doc) { + yyjson_doc_free(*doc); + } +} + +static const char* parseTermuxApi(FFBatteryOptions* options, FFlist* results) { + FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate(); + + if (ffProcessAppendStdOut(&buffer, (char* const[]) { FF_TERMUX_API_PATH, FF_TERMUX_API_PARAM, NULL })) { + return "Starting `" FF_TERMUX_API_PATH " " FF_TERMUX_API_PARAM "` failed"; + } + + yyjson_doc* FF_A_CLEANUP(wrapYyjsonFree) doc = yyjson_read_opts(buffer.chars, buffer.length, 0, NULL, NULL); + if (!doc) { + return "Failed to parse battery info"; + } + + yyjson_val* root = yyjson_doc_get_root(doc); + if (!yyjson_is_obj(root)) { + return "Battery info result is not a JSON object"; + } + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + + battery->capacity = yyjson_get_num(yyjson_obj_get(root, "percentage")); + const char* acStatus = yyjson_get_str(yyjson_obj_get(root, "plugged")); + if (acStatus) { + if (ffStrEquals(acStatus, "PLUGGED_AC")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_USB")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } else if (ffStrEquals(acStatus, "PLUGGED_WIRELESS")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + } + const char* status = yyjson_get_str(yyjson_obj_get(root, "status")); + if (status) { + if (ffStrEquals(status, "CHARGING")) { + battery->status |= FF_BATTERY_STATUS_CHARGING; + } else if (ffStrEquals(status, "DISCHARGING")) { + battery->status |= FF_BATTERY_STATUS_DISCHARGING; + } + } + + if (options->temp) { + battery->temperature = yyjson_get_num(yyjson_obj_get(root, "temperature")); + } + + return NULL; +} + +static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) { + FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate(); + if (ffProcessAppendStdOut(&buf, (char*[]) { + "/system/bin/dumpsys", + "battery", + NULL, + }) != NULL || + buf.length == 0) { + return "Executing `/system/bin/dumpsys battery` failed"; // Only works in `adb shell`, or when rooted + } + + if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) { + return "Invalid `/system/bin/dumpsys battery` result"; + } + + const char* start = buf.chars + strlen("Current Battery Service state:\n"); + + FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate(); + if (!ffParsePropLines(start, "present: ", &temp) || !ffStrbufEqualS(&temp, "true")) { + return NULL; + } + ffStrbufClear(&temp); + + FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results); + battery->temperature = FF_BATTERY_TEMP_UNSET; + battery->cycleCount = 0; + battery->timeRemaining = -1; + battery->capacity = 0; + battery->status = FF_BATTERY_STATUS_NONE; + ffStrbufInit(&battery->manufacturer); + ffStrbufInit(&battery->modelName); + ffStrbufInit(&battery->technology); + ffStrbufInit(&battery->serial); + ffStrbufInit(&battery->manufactureDate); + + if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_AC_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_USB_CONNECTED; + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) { + battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED; + } + ffStrbufClear(&temp); + + { + double level = 0, scale = 0; + if (ffParsePropLines(start, "level: ", &temp)) { + level = ffStrbufToDouble(&temp, -DBL_MAX); + } + ffStrbufClear(&temp); + + if (ffParsePropLines(start, "scale: ", &temp)) { + scale = ffStrbufToDouble(&temp, -DBL_MAX); + } + ffStrbufClear(&temp); + + if (level > 0 && scale > 0) { + battery->capacity = level * 100 / scale; + } + } + + if (options->temp) { + if (ffParsePropLines(start, "temperature: ", &temp)) { + battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET); + if (battery->temperature != FF_BATTERY_TEMP_UNSET) { + battery->temperature /= 10.0; // Android returns temperature in tenths of a degree + } + } + ffStrbufClear(&temp); + } + + ffParsePropLines(start, "technology: ", &battery->technology); + + return NULL; +} + +const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) { + const char* error = parseTermuxApi(options, results); + if (error && parseDumpsys(options, results) == NULL) { + return NULL; + } + return error; +} |