feat: add kernel ACPI support for querying battery percentage, add battery indicator to desktop, move log state to 0:/state directory
This commit is contained in:
1 parent
0a18b2cf2d
commit
e641500b40
34 files changed
+2461
-100
No files matched your search
@@ -256,6 +256,9 @@ namespace montauk::abi {
|
||||
// in one at a time. For buffers the caller is about to touch in full.
|
||||
static constexpr uint64_t SYS_ALLOC_EAGER = 187; // (bytes) -> va, 0 on failure
|
||||
|
||||
/* Battery.hpp -- ACPI battery status */
|
||||
static constexpr uint64_t SYS_BATTERYINFO = 188; // (BatteryInfo*) -> 0, -1 no battery device
|
||||
|
||||
/* Kernel-owned process capabilities. User identities may namespace
|
||||
per-user resources, but never participate in authorization decisions. */
|
||||
static constexpr uint64_t CAP_PROCESS_ADMIN = 1ULL << 0;
|
||||
@@ -808,6 +811,26 @@ namespace montauk::abi {
|
||||
uint64_t pageSize;
|
||||
};
|
||||
|
||||
// Battery status (returned by SYS_BATTERYINFO). A machine with no battery
|
||||
// device fails the call outright; `present` distinguishes a laptop with the
|
||||
// pack removed from one that is running on it.
|
||||
struct BatteryInfo {
|
||||
uint8_t present; // a battery is installed
|
||||
uint8_t charging;
|
||||
uint8_t discharging;
|
||||
uint8_t critical;
|
||||
uint8_t acOnline; // AC adapter connected
|
||||
uint8_t hasAcAdapter; // an AC adapter device exists at all
|
||||
uint8_t percent; // 0..100, 0 when capacity is unknown
|
||||
uint8_t powerUnitMilliAmps; // capacities are mAh/mA, not mWh/mW
|
||||
uint32_t remainingCapacity;
|
||||
uint32_t fullCapacity; // last full charge capacity
|
||||
uint32_t designCapacity;
|
||||
uint32_t presentRate; // mW or mA, 0 when unknown
|
||||
uint32_t voltageMv;
|
||||
int32_t minutesRemaining; // -1 when not estimable (e.g. charging)
|
||||
};
|
||||
|
||||
// Crash report (filled by kernel on process fault, returned via SYS_CRASH_REPORT)
|
||||
struct CrashReportInfo {
|
||||
int pid;
|
||||
|
||||
@@ -217,6 +217,16 @@ struct DesktopState {
|
||||
uint64_t wifi_status_time;
|
||||
|
||||
|
||||
// Battery indicator. Absent entirely on a machine with no ACPI battery
|
||||
// device, which is how a desktop keeps a clean panel.
|
||||
SvgIcon icon_battery;
|
||||
char battery_icon_name[64]; // which glyph icon_battery holds
|
||||
bool battery_present; // a battery device exists
|
||||
bool battery_popup_open;
|
||||
Rect battery_icon_rect;
|
||||
montauk::abi::BatteryInfo battery_info;
|
||||
uint64_t battery_last_poll;
|
||||
|
||||
bool vol_popup_open;
|
||||
Rect vol_icon_rect;
|
||||
int vol_level; // 0-100
|
||||
|
||||
@@ -211,6 +211,7 @@ extern "C" {
|
||||
#define MTK_SYS_SPAWN_CAPS 185
|
||||
#define MTK_SYS_SPAWN_REDIR_CAPS 186
|
||||
#define MTK_SYS_ALLOC_EAGER 187
|
||||
#define MTK_SYS_BATTERYINFO 188
|
||||
/* @SYSCALLS-END */
|
||||
|
||||
#define MTK_SOCK_TCP 1
|
||||
|
||||
@@ -639,6 +639,13 @@ namespace montauk {
|
||||
return (int)syscall1(montauk::abi::SYS_BTINFO, (uint64_t)buf);
|
||||
}
|
||||
|
||||
// ACPI battery status. Returns 0 on success, or -1 on a machine with no
|
||||
// battery device at all -- which is the signal not to draw an indicator.
|
||||
// A laptop with the pack removed still returns 0, with present == 0.
|
||||
inline int battery_info(montauk::abi::BatteryInfo* out) {
|
||||
return (int)syscall1(montauk::abi::SYS_BATTERYINFO, (uint64_t)out);
|
||||
}
|
||||
|
||||
// Wi-Fi. scan() runs a full channel sweep and blocks until it finishes or
|
||||
// timeoutMs elapses, then fills buf with the networks seen; it returns the
|
||||
// number of entries written, or -1 when no adapter is ready.
|
||||
|
||||
@@ -56,7 +56,7 @@ LDFLAGS := \
|
||||
|
||||
# ---- C++ source files ----
|
||||
|
||||
CORE_SRCS := main.cpp window.cpp panel.cpp wifi.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp desktop_catalog.cpp font_data.cpp stb_truetype_impl.cpp
|
||||
CORE_SRCS := main.cpp window.cpp panel.cpp wifi.cpp battery.cpp compose.cpp input.cpp dialogs.cpp launcher.cpp desktop_builtin.cpp desktop_catalog.cpp font_data.cpp stb_truetype_impl.cpp
|
||||
APP_SRCS := $(sort $(shell find apps -name '*.cpp' -print))
|
||||
SRCS := $(CORE_SRCS) $(APP_SRCS)
|
||||
OBJS := $(addprefix $(OBJDIR)/,$(SRCS:.cpp=.o))
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/*
|
||||
* battery.cpp
|
||||
* Panel battery indicator: the icon that only appears on a machine with a
|
||||
* battery, and the popout that puts a number on it.
|
||||
*
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "desktop_internal.hpp"
|
||||
#include <gui/mtk.hpp>
|
||||
|
||||
using namespace gui;
|
||||
|
||||
// ============================================================================
|
||||
// Geometry
|
||||
// ============================================================================
|
||||
|
||||
static constexpr int BATT_POPUP_W = 210;
|
||||
static constexpr int BATT_POLL_MS = 5000; // the kernel caches for 2s anyway
|
||||
|
||||
static int battery_popup_x(const DesktopState* ds) {
|
||||
int x = ds->battery_icon_rect.x + ds->battery_icon_rect.w - BATT_POPUP_W;
|
||||
return x < 4 ? 4 : x;
|
||||
}
|
||||
|
||||
// Rows below the headline: state, then whatever of capacity/time we can say.
|
||||
static int battery_detail_rows(const DesktopState* ds) {
|
||||
const montauk::abi::BatteryInfo& b = ds->battery_info;
|
||||
if (!b.present) return 0;
|
||||
|
||||
int rows = 2; // "Charge", then "State"
|
||||
if (b.minutesRemaining >= 0) rows++; // "Time left"
|
||||
if (b.fullCapacity > 0) rows++; // "Capacity"
|
||||
return rows;
|
||||
}
|
||||
|
||||
static int battery_popup_h(const DesktopState* ds) {
|
||||
int fh = system_font_height();
|
||||
int header = fh + 10; // title row
|
||||
int rows = battery_detail_rows(ds) * (fh + 6);
|
||||
return 10 + header + rows + 12;
|
||||
}
|
||||
|
||||
static Rect battery_popup_rect(const DesktopState* ds) {
|
||||
return {battery_popup_x(ds), PANEL_HEIGHT + 2, BATT_POPUP_W, battery_popup_h(ds)};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Icon selection
|
||||
// ============================================================================
|
||||
|
||||
// The icon pack ships one glyph per 10% step. Round to the nearest step so the
|
||||
// drawn level matches the number in the popout rather than always sitting a
|
||||
// bucket low.
|
||||
static int battery_bucket(int percent) {
|
||||
if (percent < 0) percent = 0;
|
||||
if (percent > 100) percent = 100;
|
||||
int bucket = ((percent + 5) / 10) * 10;
|
||||
return bucket > 100 ? 100 : bucket;
|
||||
}
|
||||
|
||||
static void battery_icon_path(const DesktopState* ds, char* out, int cap) {
|
||||
const montauk::abi::BatteryInfo& b = ds->battery_info;
|
||||
|
||||
if (!b.present) {
|
||||
montauk::strncpy(out, "0:/icons/battery-missing-symbolic.svg", cap);
|
||||
return;
|
||||
}
|
||||
|
||||
int bucket = battery_bucket(b.percent);
|
||||
|
||||
// A full battery on the charger gets the "charged" glyph; the pack has no
|
||||
// 100% charging variant, and "charged" is what that state means anyway.
|
||||
if (b.charging && bucket >= 100) {
|
||||
montauk::strncpy(out, "0:/icons/battery-level-100-charged-symbolic.svg", cap);
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(out, cap, "0:/icons/battery-level-%d%s-symbolic.svg",
|
||||
bucket, b.charging ? "-charging" : "");
|
||||
}
|
||||
|
||||
// Reload the glyph only when the one we want changes: at a 5 second poll a
|
||||
// laptop crosses a bucket boundary every few minutes at most, and each load
|
||||
// rasterises an SVG.
|
||||
static bool battery_refresh_icon(DesktopState* ds) {
|
||||
char path[64];
|
||||
battery_icon_path(ds, path, sizeof(path));
|
||||
|
||||
if (ds->icon_battery.pixels && montauk::streq(path, ds->battery_icon_name))
|
||||
return false;
|
||||
|
||||
SvgIcon loaded = svg_load(path, 16, 16, colors::PANEL_TEXT);
|
||||
if (!loaded.pixels) return false;
|
||||
|
||||
svg_free(ds->icon_battery);
|
||||
ds->icon_battery = loaded;
|
||||
montauk::strncpy(ds->battery_icon_name, path, sizeof(ds->battery_icon_name));
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Startup and polling
|
||||
// ============================================================================
|
||||
|
||||
void desktop_battery_init(DesktopState* ds) {
|
||||
ds->icon_battery = {nullptr, 0, 0};
|
||||
ds->battery_icon_name[0] = '\0';
|
||||
ds->battery_popup_open = false;
|
||||
ds->battery_icon_rect = {0, 0, 0, 0};
|
||||
ds->battery_last_poll = 0;
|
||||
montauk::memset(&ds->battery_info, 0, sizeof(ds->battery_info));
|
||||
|
||||
// A machine with no ACPI battery device fails the call outright, and the
|
||||
// indicator never appears. This does not change while we are running, so
|
||||
// unlike Wi-Fi there is nothing to keep looking for.
|
||||
ds->battery_present = montauk::battery_info(&ds->battery_info) == 0;
|
||||
if (ds->battery_present) battery_refresh_icon(ds);
|
||||
}
|
||||
|
||||
bool desktop_battery_poll(DesktopState* ds, uint64_t now) {
|
||||
if (!ds->battery_present) return false;
|
||||
if (ds->battery_last_poll != 0 && now - ds->battery_last_poll < BATT_POLL_MS)
|
||||
return false;
|
||||
ds->battery_last_poll = now;
|
||||
|
||||
montauk::abi::BatteryInfo fresh;
|
||||
if (montauk::battery_info(&fresh) != 0) return false;
|
||||
|
||||
bool changed = fresh.percent != ds->battery_info.percent
|
||||
|| fresh.present != ds->battery_info.present
|
||||
|| fresh.charging != ds->battery_info.charging
|
||||
|| fresh.acOnline != ds->battery_info.acOnline
|
||||
|| fresh.minutesRemaining != ds->battery_info.minutesRemaining;
|
||||
|
||||
ds->battery_info = fresh;
|
||||
if (battery_refresh_icon(ds)) changed = true;
|
||||
|
||||
// While the popout is up its numbers are on screen, so any change at all
|
||||
// has to be repainted.
|
||||
return changed || ds->battery_popup_open;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Popout
|
||||
// ============================================================================
|
||||
|
||||
|
||||
void desktop_draw_battery_popup(DesktopState* ds) {
|
||||
Framebuffer& fb = ds->fb;
|
||||
const montauk::abi::BatteryInfo& b = ds->battery_info;
|
||||
Rect popup = battery_popup_rect(ds);
|
||||
int fh = system_font_height();
|
||||
Color dim = Color::from_rgb(0x66, 0x66, 0x66);
|
||||
|
||||
draw_shadow(fb, popup.x, popup.y, popup.w, popup.h, 4, colors::SHADOW);
|
||||
fill_rounded_rect(fb, popup.x, popup.y, popup.w, popup.h, 8, colors::MENU_BG);
|
||||
draw_rect(fb, popup.x, popup.y, popup.w, popup.h, colors::BORDER);
|
||||
|
||||
int lx = popup.x + 14;
|
||||
int vx = popup.x + 92;
|
||||
int ty = popup.y + 10;
|
||||
|
||||
draw_text(fb, lx, ty, "Battery", colors::TEXT_COLOR);
|
||||
|
||||
// A coloured dot mirrors the Wi-Fi popup: green on the charger, amber when
|
||||
// the charge is getting short, red once the firmware calls it critical.
|
||||
const char* status_str;
|
||||
Color dot_color;
|
||||
if (!b.present) {
|
||||
status_str = "Not present";
|
||||
dot_color = Color::from_rgb(0x88, 0x88, 0x88);
|
||||
} else if (b.charging) {
|
||||
status_str = "Charging";
|
||||
dot_color = Color::from_rgb(0x4C, 0xAF, 0x50);
|
||||
} else if (b.critical || b.percent <= 10) {
|
||||
status_str = "Low";
|
||||
dot_color = Color::from_rgb(0xCC, 0x33, 0x33);
|
||||
} else if (b.acOnline) {
|
||||
status_str = "Plugged in";
|
||||
dot_color = Color::from_rgb(0x4C, 0xAF, 0x50);
|
||||
} else {
|
||||
status_str = "On battery";
|
||||
dot_color = (b.percent <= 25) ? Color::from_rgb(0xD0, 0x9A, 0x2E)
|
||||
: Color::from_rgb(0x66, 0x66, 0x66);
|
||||
}
|
||||
|
||||
int sw = text_width(status_str);
|
||||
int status_x = popup.x + popup.w - 14 - sw;
|
||||
fill_circle(fb, status_x - 9, ty + fh / 2, 4, dot_color);
|
||||
draw_text(fb, status_x, ty, status_str, dim);
|
||||
|
||||
ty += fh + 10;
|
||||
|
||||
if (!b.present) return;
|
||||
|
||||
char value[40];
|
||||
|
||||
snprintf(value, sizeof(value), "%d%%", (int)b.percent);
|
||||
draw_text(fb, lx, ty, "Charge", dim);
|
||||
draw_text(fb, vx, ty, value, colors::TEXT_COLOR);
|
||||
ty += fh + 6;
|
||||
|
||||
snprintf(value, sizeof(value), "%s",
|
||||
b.charging ? "Charging" : (b.acOnline ? "Not charging" : "Discharging"));
|
||||
draw_text(fb, lx, ty, "State", dim);
|
||||
draw_text(fb, vx, ty, value, colors::TEXT_COLOR);
|
||||
ty += fh + 6;
|
||||
|
||||
if (b.minutesRemaining >= 0) {
|
||||
int mins = b.minutesRemaining;
|
||||
if (mins >= 60) snprintf(value, sizeof(value), "%dh %02dm", mins / 60, mins % 60);
|
||||
else snprintf(value, sizeof(value), "%dm", mins);
|
||||
draw_text(fb, lx, ty, "Time left", dim);
|
||||
draw_text(fb, vx, ty, value, colors::TEXT_COLOR);
|
||||
ty += fh + 6;
|
||||
}
|
||||
|
||||
if (b.fullCapacity > 0) {
|
||||
const char* unit = b.powerUnitMilliAmps ? "mAh" : "mWh";
|
||||
snprintf(value, sizeof(value), "%u/%u %s",
|
||||
(unsigned)b.remainingCapacity, (unsigned)b.fullCapacity, unit);
|
||||
draw_text(fb, lx, ty, "Capacity", dim);
|
||||
draw_text(fb, vx, ty, value, colors::TEXT_COLOR);
|
||||
}
|
||||
}
|
||||
|
||||
// A click anywhere outside the popout closes it; the popout itself has nothing
|
||||
// to click, so it only has to swallow the event.
|
||||
bool desktop_battery_handle_mouse(DesktopState* ds, int mx, int my) {
|
||||
if (!ds->battery_popup_open) return false;
|
||||
|
||||
if (battery_popup_rect(ds).contains(mx, my)) return true;
|
||||
|
||||
if (!ds->battery_icon_rect.contains(mx, my))
|
||||
ds->battery_popup_open = false;
|
||||
return false;
|
||||
}
|
||||
@@ -268,6 +268,11 @@ void gui::desktop_compose(DesktopState* ds) {
|
||||
desktop_draw_wifi_popup(ds);
|
||||
}
|
||||
|
||||
// Draw battery popup if open
|
||||
if (ds->battery_popup_open) {
|
||||
desktop_draw_battery_popup(ds);
|
||||
}
|
||||
|
||||
// Draw volume popup if open
|
||||
if (ds->vol_popup_open) {
|
||||
desktop_draw_vol_popup(ds);
|
||||
|
||||
@@ -195,6 +195,12 @@ void desktop_draw_wifi_popup(gui::DesktopState* ds);
|
||||
bool desktop_wifi_handle_mouse(gui::DesktopState* ds, int mx, int my,
|
||||
bool left_pressed, int scroll);
|
||||
|
||||
// battery.cpp
|
||||
void desktop_battery_init(gui::DesktopState* ds);
|
||||
bool desktop_battery_poll(gui::DesktopState* ds, uint64_t now);
|
||||
void desktop_draw_battery_popup(gui::DesktopState* ds);
|
||||
bool desktop_battery_handle_mouse(gui::DesktopState* ds, int mx, int my);
|
||||
|
||||
// compose.cpp
|
||||
void desktop_draw_lock_screen(gui::DesktopState* ds);
|
||||
void desktop_mark_background_dirty(gui::DesktopState* ds);
|
||||
|
||||
@@ -189,6 +189,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->vol_popup_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -529,6 +530,13 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Battery popout: it has no controls, so it only swallows clicks that land
|
||||
// inside it and closes on anything else.
|
||||
if (ds->battery_popup_open && left_pressed) {
|
||||
if (desktop_battery_handle_mouse(ds, mx, my))
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle net popup clicks
|
||||
if (ds->net_popup_open && left_pressed) {
|
||||
int popup_w = 220;
|
||||
@@ -545,6 +553,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
} else if (!ds->net_icon_rect.contains(mx, my)) {
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +564,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = !ds->app_menu_open;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
@@ -571,6 +581,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
@@ -583,6 +594,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
}
|
||||
@@ -593,6 +605,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
}
|
||||
@@ -603,9 +616,22 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
}
|
||||
|
||||
// Battery icon
|
||||
if (ds->battery_icon_rect.w > 0 && ds->battery_icon_rect.contains(mx, my)) {
|
||||
ds->battery_popup_open = !ds->battery_popup_open;
|
||||
ds->app_menu_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
gui::mtk::context_menu_close(ds->ctx_menu);
|
||||
return;
|
||||
}
|
||||
|
||||
// Window indicator buttons
|
||||
int indicator_x = 40;
|
||||
for (int i = 0; i < ds->window_count; i++) {
|
||||
@@ -833,6 +859,7 @@ void gui::desktop_handle_mouse(DesktopState* ds) {
|
||||
ds->app_menu_open = false;
|
||||
ds->net_popup_open = false;
|
||||
ds->wifi_popup_open = false;
|
||||
ds->battery_popup_open = false;
|
||||
ds->vol_popup_open = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,6 +392,7 @@ void gui::desktop_init(DesktopState* ds) {
|
||||
|
||||
desktop_refresh_netifs(ds);
|
||||
desktop_wifi_init(ds);
|
||||
desktop_battery_init(ds);
|
||||
|
||||
ds->vol_popup_open = false;
|
||||
ds->vol_icon_rect = {0, 0, 0, 0};
|
||||
@@ -518,6 +519,7 @@ static bool desktop_refresh_panel_state(DesktopState* ds, uint64_t now) {
|
||||
}
|
||||
|
||||
if (desktop_wifi_poll(ds, now)) changed = true;
|
||||
if (desktop_battery_poll(ds, now)) changed = true;
|
||||
|
||||
// Event-driven sync: the kernel mixer bumps a serial on every state
|
||||
// change. Reading it is one cheap syscall; refresh only when the serial
|
||||
|
||||
@@ -210,6 +210,33 @@ void gui::desktop_draw_panel(DesktopState* ds) {
|
||||
} else {
|
||||
ds->wifi_icon_rect = {0, 0, 0, 0};
|
||||
}
|
||||
|
||||
// Battery, leftmost of the status icons. Only a machine that reported a
|
||||
// battery device at startup gets one; a desktop shows nothing here.
|
||||
if (ds->battery_present && ds->icon_battery.pixels) {
|
||||
ds->battery_icon_rect = {next_icon_x, icon_y, 16, 16};
|
||||
next_icon_x -= 16 + 10;
|
||||
|
||||
// The glyph already encodes level and charging state, and the charging
|
||||
// variants carry their own green, so it is blitted as loaded. A
|
||||
// critical battery is the one case worth shouting about.
|
||||
if (ds->battery_info.present && ds->battery_info.critical
|
||||
&& !ds->battery_info.charging) {
|
||||
uint32_t* src = ds->icon_battery.pixels;
|
||||
uint32_t tinted[256];
|
||||
for (int p = 0; p < 16 * 16; p++) {
|
||||
uint8_t a = (src[p] >> 24) & 0xFF;
|
||||
tinted[p] = ((uint32_t)a << 24) | 0x00CC3333;
|
||||
}
|
||||
fb.blit_alpha(ds->battery_icon_rect.x, icon_y, 16, 16, tinted);
|
||||
} else {
|
||||
fb.blit_alpha(ds->battery_icon_rect.x, icon_y,
|
||||
ds->icon_battery.width, ds->icon_battery.height,
|
||||
ds->icon_battery.pixels);
|
||||
}
|
||||
} else {
|
||||
ds->battery_icon_rect = {0, 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -186,18 +186,26 @@ static bool path_is_file(const char* path) {
|
||||
|
||||
#define LOG_READ_SIZE 65536
|
||||
|
||||
// Write the system log to a file on disk
|
||||
// Write the system log to a file on disk.
|
||||
//
|
||||
// 0:/state is the system's mutable runtime state: generated by the system,
|
||||
// kept across boots, and never part of the system image. The log used to live
|
||||
// in 0:/os/logs, which the protected-path table covers as CAP_SYSTEM_IMAGE
|
||||
// (Fs/ProtectedPaths.cpp) -- nothing is ever granted that capability, so every
|
||||
// create, write and delete below failed silently and the log froze at whatever
|
||||
// an earlier build had written.
|
||||
void stage_save_log() {
|
||||
// Edge case - 0:/os/logs exists and is a file
|
||||
if (path_is_file("0:/os/logs"))
|
||||
// Edge cases - 0:/state or 0:/state/logs exists and is a file
|
||||
if (path_is_file("0:/state") || path_is_file("0:/state/logs"))
|
||||
return;
|
||||
|
||||
montauk::fmkdir("0:/os/logs");
|
||||
montauk::fmkdir("0:/state");
|
||||
montauk::fmkdir("0:/state/logs");
|
||||
|
||||
// Clear any existing montaukos log
|
||||
montauk::fdelete("0:/os/logs/montaukos");
|
||||
montauk::fdelete("0:/state/logs/montaukos");
|
||||
|
||||
int handle = montauk::fcreate("0:/os/logs/montaukos");
|
||||
int handle = montauk::fcreate("0:/state/logs/montaukos");
|
||||
if (handle < 0)
|
||||
return;
|
||||
|
||||
|
||||
@@ -95,6 +95,63 @@ static void print_snapshot(const montauk::abi::PowerInfo& info) {
|
||||
print("\n");
|
||||
}
|
||||
|
||||
// Battery is reported independently of the CPU power features: a laptop under
|
||||
// emulation has neither, but a real one may well expose a battery and no HWP.
|
||||
static void print_battery() {
|
||||
montauk::abi::BatteryInfo b{};
|
||||
if (montauk::battery_info(&b) != 0) {
|
||||
print("Battery: none (no ACPI battery device)\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!b.present) {
|
||||
print("Battery: slot present, no pack installed");
|
||||
if (b.hasAcAdapter) print(b.acOnline ? " (on AC)" : " (no AC)");
|
||||
print("\n");
|
||||
return;
|
||||
}
|
||||
|
||||
print("Battery: ");
|
||||
put_u64(b.percent);
|
||||
print("%");
|
||||
|
||||
if (b.charging) print(", charging");
|
||||
else if (b.acOnline) print(", on AC, not charging");
|
||||
else print(", discharging");
|
||||
if (b.critical) print(", CRITICAL");
|
||||
print("\n");
|
||||
|
||||
if (b.fullCapacity > 0) {
|
||||
print(" Capacity: ");
|
||||
put_u64(b.remainingCapacity);
|
||||
print(" of ");
|
||||
put_u64(b.fullCapacity);
|
||||
print(b.powerUnitMilliAmps ? " mAh (design " : " mWh (design ");
|
||||
put_u64(b.designCapacity);
|
||||
print(")\n");
|
||||
}
|
||||
|
||||
if (b.presentRate > 0) {
|
||||
print(" Rate: ");
|
||||
put_u64(b.presentRate);
|
||||
print(b.powerUnitMilliAmps ? " mA\n" : " mW\n");
|
||||
}
|
||||
|
||||
if (b.minutesRemaining >= 0) {
|
||||
print(" Time left: ");
|
||||
put_u64((uint64_t)(b.minutesRemaining / 60));
|
||||
print("h ");
|
||||
put_u64((uint64_t)(b.minutesRemaining % 60));
|
||||
print("m\n");
|
||||
}
|
||||
|
||||
if (b.voltageMv > 0) {
|
||||
print(" Voltage: ");
|
||||
put_u64(b.voltageMv);
|
||||
print(" mV\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void print_watch_line(const montauk::abi::PowerInfo& info, uint64_t t) {
|
||||
put_u64(t);
|
||||
print("s ");
|
||||
@@ -124,13 +181,16 @@ extern "C" void _start() {
|
||||
montauk::abi::PowerInfo info{};
|
||||
if (!read_info(info)) {
|
||||
print("power: no CPU power management available\n");
|
||||
print("(non-Intel CPU or running under emulation)\n");
|
||||
print("(non-Intel CPU or running under emulation)\n\n");
|
||||
print_battery();
|
||||
montauk::exit(1);
|
||||
}
|
||||
|
||||
bool watch = rest[0] == 'w';
|
||||
if (!watch) {
|
||||
print_snapshot(info);
|
||||
print("\n");
|
||||
print_battery();
|
||||
montauk::exit(0);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user