/* miniapm - A tiny battery monitor Copyright 2002 Matthew Allum This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #ifdef ENABLE_NLS # include # define _(text) gettext(text) #else # define _(text) (text) #endif #ifdef USE_LIBSN #define SN_API_NOT_YET_FROZEN 1 #include #endif #ifdef HAVE_APM_H /* Linux */ #include #endif #ifdef HAVE_APMVAR_H /* *BSD */ #include #include #include #include #define APMDEV "/dev/apm" enum apm_state { NORMAL, SUSPENDING, STANDING_BY }; struct apm_reply { int vno; enum apm_state newstate; struct apm_power_info batterystate; }; #endif #define TIME_LEFT 0 #define PERCENTAGE 1 #define AC_POWER 2 #define WIN_WIDTH 8 #define WIN_HEIGHT 14 #define CLOCK_DISP 1 #define BATT_DISP 0 #ifndef AC_LINE_STATUS_ON #define AC_LINE_STATUS_ON 1 #endif #ifndef MB_HAVE_PNG #error PNG is required #endif #define CONTEXT_APP "/usr/bin/gpe-info" #define CONTEXT_APP_ARGS "battery" #define CONTEXT_APP_WANT_SN 1 #define CHECK_TIME_SLICE 2 /* 2 seconds between battery checks */ #define NR_IMAGES 10 static int apm_vals[3]; static MBPixbuf *pb; static MBPixbufImage *images[NR_IMAGES]; static int time_left_alerts[] = { 0, 2, 5, 10, 20 }; /* times to alert on */ static int time_left_idx = 4; static int window_h, window_w; static Bool ac_power = False; static char* ThemeName = NULL; static int last_percentage, last_ac; #ifdef USE_LIBSN static SnDisplay *sn_dpy; static void sn_activate(char *name, char *exec_str) { SnLauncherContext *context; pid_t child_pid = 0; context = sn_launcher_context_new (sn_dpy, 0); sn_launcher_context_set_name (context, name); sn_launcher_context_set_binary_name (context, exec_str); sn_launcher_context_initiate (context, "monoluanch launch", exec_str, CurrentTime); switch ((child_pid = fork ())) { case -1: fprintf (stderr, "Fork failed\n" ); break; case 0: sn_launcher_context_setup_child_process (context); mb_exec(exec_str); fprintf (stderr, "Failed to exec %s \n", exec_str); _exit (1); break; } } #endif #ifdef USE_PROC_APM /* Read battery state straight out of /proc/apm, as provided by the kernel's * APM emulation layer (CONFIG_APM_EMULATION). This avoids a dependency on * libapm/apm.h, which is what the HAVE_APM_H path below needs. * * Format (drivers/char/apm-emulation.c, proc_apm_show): * driver-ver bios-ver flags ac-line batt-status batt-flag batt-life% time units * e.g. "1.13 1.2 0x03 0x01 0x03 0x09 98% -1 ?" * * On this board the provider is sharpsl_pm, whose apm_get_power_status only * fills in ac_line_status, battery_status, battery_flag and battery_life -- * it leaves time and units alone, so they stay at the kernel's defaults of * -1 and "?". Expect no runtime estimate here, only a percentage; every * consumer of TIME_LEFT in this applet is guarded on "> 0", so -1 is fine. */ #define PROC_APM_PATH "/proc/apm" static int read_apm(int *values) { FILE *f; char units[16]; char line[256]; int ac = -1, batt_status = 0, batt_flag = 0, percent = -1, time_left = -1; values[TIME_LEFT] = -1; values[PERCENTAGE] = -1; values[AC_POWER] = 0; f = fopen(PROC_APM_PATH, "r"); if (f == NULL) return 0; if (fgets(line, sizeof(line), f) == NULL) { fclose(f); return 0; } fclose(f); units[0] = '\0'; if (sscanf(line, "%*s %*s %*x %x %x %x %d%% %d %15s", &ac, &batt_status, &batt_flag, &percent, &time_left, units) < 4) return 0; /* This applet counts TIME_LEFT in MINUTES: the tooltip prints * TIME_LEFT/60 as hours and TIME_LEFT%60 as minutes, and * time_left_alerts[] holds { 0, 2, 5, 10, 20 }. /proc/apm reports * either unit and says which in the last field, so scale seconds down * rather than minutes up. */ if (time_left > 0 && units[0] == 's') /* "sec" */ time_left /= 60; values[TIME_LEFT] = time_left; values[PERCENTAGE] = percent; values[AC_POWER] = (ac == 1) ? AC_LINE_STATUS_ON : 0; return 1; } #elif defined(USE_ACPI_LINUX) #define ACPI_PREFIX "/proc/acpi" #define BAT_PREFIX "battery/BAT0" #define AC_PREFIX "ac_adapter/AC0" /* ACPI sometimes reports incorrect values, especially * shortly after unplugging the AC power. Tweak this for * your hardware. * See /proc/acpi/battery/BAT0/state:"present rate" for * "normal" values on your system. */ #define ACPI_RATE_MIN 300 #define ACPI_RATE_MAX 5000 struct avg_consumption { unsigned long avg_values[100]; int avg_index; int avg_records; int countdown; }; static struct avg_consumption a; static int read_apm(int *values) { enum ac_state { AC_UNKNOWN = -1, AC_OFFLINE = 0, AC_ONLINE = 1 }; enum bat_state { BAT_UNKNOWN = -1, BAT_CHARGING = 1, BAT_DISCHARGING = 2 }; struct entry { char *key; char *value; char *unit; }; struct acpi_status { enum ac_state ac_state; enum bat_state bat_state; unsigned long design_capacity; unsigned long remaining_capacity; unsigned long present_rate; }; static int line_parse(char *line, struct entry *e) { char *ptr; ptr = strchr(line, ':'); if (!ptr) return -1; e->key = line; *ptr = 0; while (*++ptr && *ptr == ' '); e->value = ptr; while (*++ptr && *ptr != '\n' && *ptr != ' '); *ptr = 0; if (*++ptr) { e->unit = ptr; while (*++ptr && *ptr != '\n'); *ptr = 0; } else e->unit = 0; return 0; } FILE *f; char line[1024]; struct entry e; struct acpi_status s; memset (&s, 0, sizeof(s)); f = fopen(ACPI_PREFIX "/" AC_PREFIX "/state", "r"); if (f) { while (fgets(line, sizeof(line), f)) { if (line_parse(line, &e) < 0) continue; if (strcmp(e.key, "state") == 0) { if(strcmp(e.value, "on-line") == 0) s.ac_state = AC_ONLINE; else if (strcmp(e.value, "off-line") == 0) s.ac_state = AC_OFFLINE; else s.ac_state = AC_UNKNOWN; } } fclose(f); } f = fopen(ACPI_PREFIX "/" BAT_PREFIX "/info", "r"); if (f) { while (fgets(line, sizeof(line), f)) { if (line_parse(line, &e) < 0) continue; if (strcmp(e.key, "design capacity") == 0) sscanf(e.value, "%lu", &s.design_capacity); } fclose(f); } f = fopen(ACPI_PREFIX "/" BAT_PREFIX "/state", "r"); if (f) { while (fgets(line, sizeof(line), f)) { if (line_parse(line, &e) < 0) continue; if (strcmp(e.key, "remaining capacity") == 0) sscanf(e.value, "%lu", &s.remaining_capacity); else if (strcmp(e.key, "charging state") == 0) { if (strcmp(e.value, "charging") == 0) s.bat_state = BAT_CHARGING; else if (strcmp(e.value, "discharging") == 0) s.bat_state = BAT_DISCHARGING; else s.bat_state = BAT_UNKNOWN; } else if (strcmp(e.key, "present rate") == 0) sscanf(e.value, "%lu", &s.present_rate); } fclose(f); } /* Don't estimate TIME_LEFT on current power consumption. Instead * compute some average and ignore too low/high values. */ values[TIME_LEFT] = 0; if (s.bat_state != BAT_DISCHARGING && !a.countdown) { memset(&a, 0, sizeof(a)); a.countdown = 10; } else if (s.bat_state == BAT_DISCHARGING && s.present_rate) { int num_values = sizeof(a.avg_values)/sizeof(a.avg_values[0]); unsigned long sum = 0; int i; if (a.countdown) { a.countdown--; goto bail_out; } if (s.present_rate < ACPI_RATE_MIN || s.present_rate > ACPI_RATE_MAX) { goto bail_out; } a.avg_values[a.avg_index++] = s.present_rate; a.avg_index %= num_values; if (a.avg_records < num_values) a.avg_records++; if (a.avg_records > 10) { for (i=0; iminutes_left; values[PERCENTAGE] = api->battery_life; values[AC_POWER] = api->ac_state; return 1; } #endif #endif /* Use ACPI */ void fork_exec(char *cmd) { switch (fork()) { case 0: setpgid(0, 0); /* Stop us killing child */ mb_exec(cmd); fprintf(stderr, "minitime: Failed to Launch '%s'", cmd); exit(1); case -1: fprintf(stderr, "minitime: Failed to Launch '%s'", cmd); break; } } void paint_callback (MBTrayApp *app, Drawable drw ) { int x, y; MBPixbufImage *img = NULL; MBPixbufImage *img_backing = NULL; while (!read_apm(apm_vals)) usleep(50000L); if (last_percentage == apm_vals[PERCENTAGE] && last_ac == apm_vals[AC_POWER]) return; img_backing = mb_tray_app_get_background (app, pb); /* Psion's battery-monitor.patch: pick one of nine states from their own artwork rather than drawing a coloured bar, and centre it in the slot. images[9] is plug.png, shown whenever AC is connected. */ /* the low-battery bubble below still tests this, and the bar-drawing code that used to set it has gone */ ac_power = (apm_vals[AC_POWER] == AC_LINE_STATUS_ON) ? True : False; if (apm_vals[AC_POWER] == AC_LINE_STATUS_ON) { img = images[9]; } else if (apm_vals[PERCENTAGE] <= 5) { img = images[0]; } else if (apm_vals[PERCENTAGE] <= 18) { img = images[1]; } else if (apm_vals[PERCENTAGE] <= 30) { img = images[2]; } else if (apm_vals[PERCENTAGE] <= 43) { img = images[3]; } else if (apm_vals[PERCENTAGE] <= 56) { img = images[4]; } else if (apm_vals[PERCENTAGE] <= 68) { img = images[5]; } else if (apm_vals[PERCENTAGE] <= 81) { img = images[6]; } else if (apm_vals[PERCENTAGE] <= 94) { img = images[7]; } else { img = images[8]; } if (img == NULL) return; x = (window_w - mb_pixbuf_img_get_width (img)) / 2; y = (window_h - mb_pixbuf_img_get_height (img)) / 2; mb_pixbuf_img_composite(pb, img_backing, img, x, y); /* Bubble alerts */ if ((time_left_idx > 0) && !ac_power && apm_vals[PERCENTAGE] > 0 /* && apm_vals[TIME_LEFT] > 0 XXX Less strict */ && (apm_vals[TIME_LEFT] < time_left_alerts[time_left_idx])) { char tray_msg[256]; sprintf(tray_msg, _("Battery power very low !\n\nTime Left: %.2i minutes"), time_left_alerts[time_left_idx]); mb_tray_app_tray_send_message(app, tray_msg, 0); time_left_idx--; } else if (time_left_idx < 4 && apm_vals[TIME_LEFT] > time_left_alerts[time_left_idx+1]) { time_left_idx++; } mb_pixbuf_img_render_to_drawable(pb, img_backing, drw, 0, 0); mb_pixbuf_img_free( pb, img_backing ); last_percentage = apm_vals[PERCENTAGE]; last_ac = apm_vals[AC_POWER]; } void resize_callback (MBTrayApp *app, int w, int h ) { window_h = h; window_w = w; } void load_icon(void) { int i; static const char *names[] = { "battery_empty.png", "battery_12.5%.png", "battery_25%.png", "battery_37.5%.png", "battery_50%.png", "battery_62.5%.png", "battery_75%.png", "battery_87.5%.png", "battery_full.png", "plug.png" }; last_percentage = -1; last_ac = -1; for (i = 0; i < NR_IMAGES; i++) { char path[256]; sprintf (path, "/usr/share/pixmaps/battery/%s", names[i]); images[i] = mb_pixbuf_img_new_from_file(pb, path); } } void button_callback (MBTrayApp *app, int x, int y, Bool is_released ) { char tray_msg[256]; if (!is_released) return; if (apm_vals[AC_POWER] == AC_LINE_STATUS_ON) { if (apm_vals[PERCENTAGE] > 0 && apm_vals[PERCENTAGE] <= 100 ) sprintf(tray_msg, _("AC Connected\nCharging: %.2i %%\n") , apm_vals[PERCENTAGE]); else sprintf(tray_msg, _("AC Connected\nFully charged.\n")); } else { if (apm_vals[PERCENTAGE] > 0 && apm_vals[PERCENTAGE] <= 100 ) { if (apm_vals[TIME_LEFT] > 0 ) { sprintf(tray_msg, _("Battery Power\nJuice %.2i %%\nTime left: %.2i h. %.2i min.\n"), apm_vals[PERCENTAGE], (apm_vals[TIME_LEFT]/60), (apm_vals[TIME_LEFT]%60)); } else { sprintf(tray_msg, _("Battery Power\nJuice %.2i %%\n"), apm_vals[PERCENTAGE]); } } else sprintf(tray_msg, _("Battery Power\n Device read error.\n")); } mb_tray_app_tray_send_message(app, tray_msg, 5000); } void timeout_callback ( MBTrayApp *app ) { mb_tray_app_repaint (app); } void context_callback ( MBTrayApp *app ) { #ifdef USE_LIBSN if (CONTEXT_APP_WANT_SN) { sn_activate(CONTEXT_APP, CONTEXT_APP " " CONTEXT_APP_ARGS); return; } #endif fork_exec(CONTEXT_APP " " CONTEXT_APP_ARGS); } static Bool file_exists(char *filename) { struct stat st; /* XXX Should probably check if exe too */ if (stat(filename, &st)) return False; return True; } int main( int argc, char *argv[]) { MBTrayApp *app = NULL; struct timeval tv; #if ENABLE_NLS setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, DATADIR "/locale"); bind_textdomain_codeset (PACKAGE, "UTF-8"); textdomain (PACKAGE); #endif app = mb_tray_app_new ( _("Battery Monitor"), resize_callback, paint_callback, &argc, &argv ); pb = mb_pixbuf_new(mb_tray_app_xdisplay(app), mb_tray_app_xscreen(app)); memset(&tv,0,sizeof(struct timeval)); tv.tv_sec = CHECK_TIME_SLICE; mb_tray_app_set_timeout_callback (app, timeout_callback, &tv); mb_tray_app_set_button_callback (app, button_callback ); load_icon(); mb_tray_app_set_icon(app, pb, images[4]); if (file_exists(CONTEXT_APP)) { mb_tray_app_set_context_info (app, _("More info")); mb_tray_app_set_context_callback (app, context_callback); } #ifdef USE_LIBSN sn_dpy = sn_display_new (mb_tray_app_xdisplay(app), NULL, NULL); #endif mb_tray_app_main (app); return 1; }