a1cd5cadce
Five years is long enough to have a release candidate, I think. :)
978 lines
27 KiB
C
978 lines
27 KiB
C
/* apm/acpi dockapp - phear it 1.34
|
|
* Copyright (C) 2000, 2001, 2002 timecop@japan.co.jp
|
|
*
|
|
* 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 of the License, 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
* 02110-1301, USA.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
|
|
#include <dockapp.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
|
|
#include <X11/X.h>
|
|
#include <X11/Xlib.h>
|
|
#include <X11/Xutil.h>
|
|
#include <X11/extensions/shape.h>
|
|
#include <X11/xpm.h>
|
|
|
|
#include "libacpi.h"
|
|
#include "wmacpi.h"
|
|
|
|
#define WMACPI_VER "2.2"
|
|
|
|
/* main pixmap */
|
|
#ifdef LOW_COLOR
|
|
#include "master_low.xpm"
|
|
static char **master_xpm = master_low_xpm;
|
|
#else
|
|
#include "master.xpm"
|
|
#endif
|
|
|
|
/* Do NOT change the BASE_PERIOD without reading the code ... */
|
|
#define BASE_PERIOD 100000 /* base period, 100 ms (in usecs) */
|
|
|
|
struct dockapp {
|
|
int x_fd; /* X11 fd */
|
|
Display *display; /* display */
|
|
Window win; /* main window */
|
|
Pixmap pixmap; /* main pixmap */
|
|
Pixmap mask; /* mask pixmap */
|
|
Pixmap text; /* pixmap for text scroller */
|
|
unsigned short width; /* width of pixmap */
|
|
unsigned short height; /* height of pixmap */
|
|
int screen; /* current screen */
|
|
int tw; /* text width inside text pixmap */
|
|
int update; /* need to redraw? */
|
|
int blink; /* should we blink the LED? (critical battery) */
|
|
int bell; /* bell on critical low, or not? */
|
|
int scroll; /* scroll message text? */
|
|
int scroll_reset; /* reset the scrolling text */
|
|
int percent;
|
|
int period_length; /* length of the polling period, multiple of BASE_PERIOD */
|
|
};
|
|
|
|
/* globals */
|
|
struct dockapp *dockapp;
|
|
/* global_t *globals; */
|
|
|
|
/* this gives us a variable scroll rate, depending on the importance of the
|
|
* message . . . */
|
|
#define DEFAULT_SCROLL_RESET 150;
|
|
int scroll_reset = DEFAULT_SCROLL_RESET;
|
|
|
|
/* copy a chunk of pixmap around the app */
|
|
static void copy_xpm_area(int x, int y, int w, int h, int dx, int dy)
|
|
{
|
|
XCopyArea(DADisplay, dockapp->pixmap, dockapp->pixmap,
|
|
DAGC, x, y, w, h, dx, dy);
|
|
dockapp->update = 1;
|
|
}
|
|
|
|
/* display AC power symbol */
|
|
static void display_power_glyph(void)
|
|
{
|
|
copy_xpm_area(67, 38, 12, 7, 6, 17);
|
|
}
|
|
|
|
/* get rid of AC power symbol */
|
|
static void kill_power_glyph(void)
|
|
{
|
|
copy_xpm_area(67, 48, 12, 7, 6, 17);
|
|
}
|
|
|
|
/* display battery symbol */
|
|
static void display_battery_glyph(void)
|
|
{
|
|
copy_xpm_area(82, 38, 12, 7, 20, 17);
|
|
}
|
|
|
|
/* get rid of battery symbol */
|
|
static void kill_battery_glyph(void)
|
|
{
|
|
copy_xpm_area(82, 48, 12, 7, 20, 17);
|
|
}
|
|
|
|
/* clear the time display */
|
|
static void clear_time_display(void)
|
|
{
|
|
copy_xpm_area(114, 76, 31, 11, 7, 32);
|
|
}
|
|
|
|
/* set time display to -- -- */
|
|
static void invalid_time_display(void)
|
|
{
|
|
copy_xpm_area(122, 14, 31, 11, 7, 32);
|
|
}
|
|
|
|
static void reset_scroll(void) {
|
|
dockapp->scroll_reset = 1;
|
|
}
|
|
|
|
static void clear_text_area(void) {
|
|
copy_xpm_area(66, 9, 52, 7, 6, 50);
|
|
}
|
|
|
|
static void redraw_window(void)
|
|
{
|
|
if (dockapp->update) {
|
|
XCopyArea(dockapp->display, dockapp->pixmap, dockapp->win,
|
|
DAGC, 0, 0, 64, 64, 0, 0);
|
|
dockapp->update = 0;
|
|
}
|
|
}
|
|
|
|
static void new_window(char *display, char *name, int argc, char **argv)
|
|
{
|
|
XSizeHints *hints;
|
|
|
|
/* Initialise the dockapp window and appicon */
|
|
DAOpenDisplay(display, argc, argv);
|
|
DACreateIcon(name, 64, 64, argc, argv);
|
|
dockapp->display = DADisplay;
|
|
dockapp->x_fd = XConnectionNumber(dockapp->display);
|
|
dockapp->win = DAWindow;
|
|
|
|
XSelectInput(dockapp->display, dockapp->win,
|
|
ExposureMask | ButtonPressMask | ButtonReleaseMask |
|
|
StructureNotifyMask);
|
|
|
|
/* create the main pixmap . . . */
|
|
DAMakePixmapFromData(master_xpm, &dockapp->pixmap, &dockapp->mask,
|
|
&dockapp->width, &dockapp->height);
|
|
DASetPixmap(dockapp->pixmap);
|
|
DASetShape(dockapp->mask);
|
|
|
|
/* text area is 318x7, or 53 characters long */
|
|
dockapp->text = XCreatePixmap(dockapp->display, dockapp->win, 318, 7,
|
|
DefaultDepth(dockapp->display,
|
|
dockapp->screen));
|
|
if (!dockapp->text) {
|
|
pfatal("FATAL: Cannot create text scroll pixmap!\n");
|
|
exit(1);
|
|
}
|
|
|
|
/* force the window to stay this size - otherwise the user could
|
|
* resize us and see our panties^Wmaster pixmap . . . */
|
|
hints = XAllocSizeHints();
|
|
if(hints) {
|
|
hints->flags |= PMinSize | PMaxSize;
|
|
hints->min_width = 64;
|
|
hints->max_width = 64;
|
|
hints->min_height = 64;
|
|
hints->max_height = 64;
|
|
XSetWMNormalHints(dockapp->display, dockapp->win, hints);
|
|
XFree(hints);
|
|
}
|
|
|
|
DAShow();
|
|
}
|
|
|
|
static void copy_to_text_buffer(int sx, int sy, int w, int h, int dx, int dy)
|
|
{
|
|
XCopyArea(dockapp->display, dockapp->pixmap, dockapp->text,
|
|
DAGC, sx, sy, w, h, dx, dy);
|
|
}
|
|
|
|
static void copy_to_text_area(int sx, int sy, int w, int h, int dx, int dy)
|
|
{
|
|
XCopyArea(dockapp->display, dockapp->text, dockapp->pixmap,
|
|
DAGC, sx, sy, w, h, dx, dy);
|
|
}
|
|
|
|
static void scroll_text(void)
|
|
{
|
|
static int start, end, stop;
|
|
int x = 6; /* x coord of the start of the text area */
|
|
int y = 50; /* y coord */
|
|
int width = 51; /* width of the text area */
|
|
int height = 7; /* height of the text area */
|
|
int tw = dockapp->tw; /* width of the rendered text */
|
|
int sx, dx, w;
|
|
|
|
if (!dockapp->scroll)
|
|
return;
|
|
|
|
/*
|
|
* Conceptually this is viewing the text through a scrolling
|
|
* window - the window starts out with the end immediately before
|
|
* the text, and stops when the start of the window is immediately
|
|
* after the end of the text.
|
|
*
|
|
* We begin with the start of the window at pixel (0 - width) and
|
|
* as we scroll we render only the portion of the window above
|
|
* pixel 0. The destination of the copy during this period starts
|
|
* out at the end of the text area and works left as more of the
|
|
* text is being copied, until a full window is being copied.
|
|
*
|
|
* As the end of the window moves out past the end of the text, we
|
|
* want to keep the destination at the beginning of the text area,
|
|
* but copy a smaller and smaller chunk of the text. Eventually the
|
|
* start of the window will scroll past the end of the text, at
|
|
* which point we stop doing any work and wait to be reset.
|
|
*/
|
|
|
|
if (dockapp->scroll_reset) {
|
|
start = 0 - width;
|
|
end = 0;
|
|
stop = 0;
|
|
clear_text_area();
|
|
dockapp->scroll_reset = 0;
|
|
}
|
|
|
|
if (stop)
|
|
return;
|
|
|
|
w = 52;
|
|
if (end < 52)
|
|
w = end;
|
|
else if (end > tw)
|
|
w = 52 - (end - tw);
|
|
|
|
dx = x + 52 - w;
|
|
if (end > tw)
|
|
dx = x;
|
|
|
|
sx = start;
|
|
if (start < 0)
|
|
sx = 0;
|
|
|
|
if (start > tw)
|
|
stop = 1;
|
|
|
|
clear_text_area();
|
|
copy_to_text_area(sx, 0, w, height, dx, y);
|
|
start += 2;
|
|
end += 2;
|
|
|
|
dockapp->update = 1;
|
|
}
|
|
|
|
static void render_text(char *string)
|
|
{
|
|
int i, c, k;
|
|
|
|
/* drop out immediately if scrolling is disabled - we don't render
|
|
* any text at all, since there's not much else we could do
|
|
* sensibly without scrolling. */
|
|
if (!dockapp->scroll)
|
|
return;
|
|
|
|
if (strlen(string) > 53)
|
|
return;
|
|
|
|
/* prepare the text area by clearing it */
|
|
for (i = 0; i < 54; i++) {
|
|
copy_to_text_buffer(133, 57, 6, 8, i * 6, 0);
|
|
}
|
|
k = 0;
|
|
|
|
for (i = 0; string[i]; i++) {
|
|
c = toupper(string[i]);
|
|
if (c >= 'A' && c <= 'Z') { /* letter */
|
|
c = c - 'A';
|
|
copy_to_text_buffer(c * 6, 67, 6, 7, k, 0);
|
|
} else if (c >= '0' && c <= '9') { /* number */
|
|
c = c - '0';
|
|
copy_to_text_buffer(c * 6 + 66, 58, 6, 7, k, 0);
|
|
} else if (c == '.') {
|
|
copy_to_text_buffer(140, 58, 6, 7, k, 0);
|
|
} else if (c == '-') {
|
|
copy_to_text_buffer(126, 58, 6, 7, k, 0);
|
|
}
|
|
k += 6;
|
|
}
|
|
dockapp->tw = k; /* length of text segment */
|
|
/* re-scroll the message */
|
|
reset_scroll();
|
|
scroll_text();
|
|
}
|
|
|
|
static void clear_percentage(void)
|
|
{
|
|
/* clear the number */
|
|
copy_xpm_area(95, 47, 21, 9, 37, 16);
|
|
/* clear the bar */
|
|
copy_xpm_area(66, 18, 54, 8, 5, 5);
|
|
|
|
dockapp->percent = -1;
|
|
}
|
|
|
|
static void display_percentage(int percent)
|
|
{
|
|
unsigned int bar;
|
|
int width = 54; /* width of the bar */
|
|
float ratio = 100.0/width; /* ratio between the current percentage
|
|
* and the number of pixels in the bar */
|
|
|
|
if (percent == -1)
|
|
percent = 0;
|
|
|
|
if (dockapp->percent == percent)
|
|
return;
|
|
|
|
if (percent < 0)
|
|
percent = 0;
|
|
if (percent > 100)
|
|
percent = 100;
|
|
|
|
if (dockapp->percent == -1)
|
|
copy_xpm_area(127, 28, 5, 7, 52, 17);
|
|
|
|
if (percent < 100) { /* 0 - 99 */
|
|
copy_xpm_area(95, 48, 8, 7, 37, 17);
|
|
if (percent >= 10)
|
|
copy_xpm_area((percent / 10) * 6 + 67, 28, 5, 7, 40, 17);
|
|
copy_xpm_area((percent % 10) * 6 + 67, 28, 5, 7, 46, 17);
|
|
} else
|
|
copy_xpm_area(95, 37, 21, 9, 37, 16); /* 100% */
|
|
dockapp->percent = percent;
|
|
|
|
bar = (int)((float)percent / ratio);
|
|
|
|
copy_xpm_area(66, 0, bar, 8, 5, 5);
|
|
if (bar < 54)
|
|
copy_xpm_area(66 + bar, 18, 54 - bar, 8, bar + 5, 5);
|
|
}
|
|
|
|
static void display_time(int minutes)
|
|
{
|
|
static int ohour = -1, omin = -1;
|
|
int hour, min, tmp;
|
|
|
|
if (minutes <= 0) { /* error - clear the display */
|
|
invalid_time_display();
|
|
ohour = omin = -1;
|
|
return;
|
|
}
|
|
|
|
/* render time on the display */
|
|
hour = minutes / 60;
|
|
/* our display area only fits %2d:%2d, so we need to make sure
|
|
* what we're displaying will fit in those constraints. I don't
|
|
* think we're likely to see any batteries that do more than
|
|
* 100 hours any time soon, so it's fairly safe. */
|
|
if (hour >= 100) {
|
|
hour = 99;
|
|
min = 59;
|
|
} else
|
|
min = minutes % 60;
|
|
|
|
if (hour == ohour && min == omin)
|
|
return;
|
|
|
|
tmp = hour / 10;
|
|
copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 7, 32);
|
|
tmp = hour % 10;
|
|
copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 14, 32);
|
|
tmp = min / 10;
|
|
copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 25, 32);
|
|
tmp = min % 10;
|
|
copy_xpm_area(tmp * 7 + 1, 76, 6, 11, 32, 32);
|
|
copy_xpm_area(71, 76, 3, 11, 21, 32);
|
|
ohour = hour;
|
|
omin = min;
|
|
}
|
|
|
|
/*
|
|
* The reworked state handling stuff.
|
|
*/
|
|
|
|
/* set the current state of the power panel */
|
|
enum panel_states {
|
|
PS_AC,
|
|
PS_BATT,
|
|
PS_NULL,
|
|
};
|
|
|
|
static void really_blink_power_glyph(void)
|
|
{
|
|
static int counter = 0;
|
|
|
|
if (counter == 10)
|
|
display_power_glyph();
|
|
else if (counter == 20)
|
|
kill_power_glyph();
|
|
else if (counter > 30)
|
|
counter = 0;
|
|
|
|
counter += dockapp->period_length;
|
|
}
|
|
|
|
static void blink_power_glyph(void)
|
|
{
|
|
if (dockapp->blink)
|
|
really_blink_power_glyph();
|
|
}
|
|
|
|
static void really_blink_battery_glyph(void)
|
|
{
|
|
static int counter = 0;
|
|
|
|
if (counter == 10)
|
|
display_battery_glyph();
|
|
else if (counter == 20)
|
|
kill_battery_glyph();
|
|
else if (counter > 30)
|
|
counter = 0;
|
|
|
|
counter += dockapp->period_length;
|
|
}
|
|
|
|
static void blink_battery_glyph(void)
|
|
{
|
|
if (dockapp->blink)
|
|
really_blink_battery_glyph();
|
|
}
|
|
|
|
static void set_power_panel(global_t *globals)
|
|
{
|
|
static enum panel_states power = PS_NULL;
|
|
battery_t *binfo = globals->binfo;
|
|
adapter_t *ap = &globals->adapter;
|
|
|
|
if (ap->power == AC) {
|
|
if (power != PS_AC) {
|
|
power = PS_AC;
|
|
kill_battery_glyph();
|
|
display_power_glyph();
|
|
}
|
|
} else if (ap->power == BATT) {
|
|
if (power != PS_BATT) {
|
|
power = PS_BATT;
|
|
kill_power_glyph();
|
|
display_battery_glyph();
|
|
}
|
|
}
|
|
|
|
if (globals->battery_count > 0) {
|
|
if (binfo->charge_state == CHARGE)
|
|
blink_power_glyph();
|
|
|
|
if ((binfo->state == CRIT) && (ap->power == BATT))
|
|
blink_battery_glyph();
|
|
}
|
|
}
|
|
|
|
void scroll_faster(double factor) {
|
|
scroll_reset = scroll_reset * factor;
|
|
}
|
|
|
|
void scroll_slower(double factor) {
|
|
scroll_reset = scroll_reset * factor;
|
|
}
|
|
|
|
void reset_scroll_speed(void) {
|
|
scroll_reset = DEFAULT_SCROLL_RESET;
|
|
}
|
|
|
|
/*
|
|
* The message that needs to be displayed needs to be decided
|
|
* according to a heirarchy: a message like not present needs to take
|
|
* precedence over a global thing like the current power status, and
|
|
* something like a low battery warning should take precedence over
|
|
* the "on battery" message. Likewise, a battery charging message
|
|
* needs to take precedence over the on ac power message. The other
|
|
* question is how much of a precedence local messages should take
|
|
* over global ones . . .
|
|
*
|
|
* So, there are three possible sets of messages: not present, on-line
|
|
* and off-line messages. We need to decide which of those sets is
|
|
* appropriate right now, and then decide within them.
|
|
*/
|
|
enum messages {
|
|
M_NB, /* no batteries */
|
|
M_NP, /* not present */
|
|
M_AC, /* on ac power */
|
|
M_CH, /* battery charging */
|
|
M_BATT, /* on battery */
|
|
M_LB, /* low battery */
|
|
M_CB, /* critical low battery */
|
|
M_HCB, /* battery reported critical capacity state */
|
|
M_NULL, /* empty starting state */
|
|
};
|
|
|
|
static void set_message(global_t *globals)
|
|
{
|
|
static enum messages state = M_NULL;
|
|
battery_t *binfo = globals->binfo;
|
|
adapter_t *ap = &globals->adapter;
|
|
|
|
if (globals->battery_count == 0) {
|
|
if (state != M_NB) {
|
|
state = M_NB;
|
|
reset_scroll_speed();
|
|
render_text("no batteries");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* battery not present case */
|
|
if (!binfo->present) {
|
|
if (state != M_NP) {
|
|
state = M_NP;
|
|
reset_scroll_speed();
|
|
render_text("not present");
|
|
}
|
|
} else if (ap->power == AC) {
|
|
if (binfo->charge_state == CHARGE) {
|
|
if (state != M_CH) {
|
|
state = M_CH;
|
|
reset_scroll_speed();
|
|
render_text("battery charging");
|
|
}
|
|
} else {
|
|
if (state != M_AC) {
|
|
state = M_AC;
|
|
reset_scroll_speed();
|
|
render_text("on ac power");
|
|
}
|
|
}
|
|
} else {
|
|
if (binfo->state == CRIT) {
|
|
if (state != M_CB) {
|
|
state = M_CB;
|
|
scroll_faster(0.75);
|
|
render_text("critical low battery");
|
|
}
|
|
} else if (binfo->state == LOW) {
|
|
if (state != M_LB) {
|
|
state = M_LB;
|
|
scroll_faster(0.85);
|
|
render_text("low battery");
|
|
}
|
|
} else {
|
|
if (state != M_BATT) {
|
|
state = M_BATT;
|
|
reset_scroll_speed();
|
|
render_text("on battery");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void set_time_display(global_t *globals)
|
|
{
|
|
if (globals->battery_count == 0) {
|
|
invalid_time_display();
|
|
return;
|
|
}
|
|
|
|
if (globals->binfo->charge_state == CHARGE)
|
|
display_time(globals->binfo->charge_time);
|
|
else if (globals->binfo->charge_state == DISCHARGE)
|
|
display_time(globals->rtime);
|
|
else
|
|
invalid_time_display();
|
|
}
|
|
|
|
void clear_batt_id_area(void)
|
|
{
|
|
copy_xpm_area(125, 40, 7, 11, 51, 32);
|
|
}
|
|
|
|
void set_batt_id_area(int bno)
|
|
{
|
|
int w = 7; /* Width of the number */
|
|
int h = 11; /* Height of the number */
|
|
int dx = 50; /* x coord of the target area */
|
|
int dy = 32; /* y coord of the target area */
|
|
int sx = (bno + 1) * 7; /* source x coord */
|
|
int sy = 76; /* source y coord */
|
|
|
|
copy_xpm_area(sx, sy, w, h, dx, dy);
|
|
}
|
|
|
|
#define VERSION "wmacpi version " WMACPI_VER "\nUsing libacpi version " LIBACPI_VER
|
|
|
|
void cli_wmacpi(global_t *globals, int samples)
|
|
{
|
|
int i, j, sleep_time = 0;
|
|
battery_t *binfo;
|
|
adapter_t *ap;
|
|
|
|
pdebug("samples: %d\n", samples);
|
|
if(samples > 1)
|
|
sleep_time = 1000000/samples;
|
|
|
|
/* we want to acquire samples over some period of time, so . . . */
|
|
for(i = 0; i < samples + 2; i++) {
|
|
for(j = 0; j < globals->battery_count; j++)
|
|
acquire_batt_info(globals, j);
|
|
acquire_global_info(globals);
|
|
usleep(sleep_time);
|
|
}
|
|
|
|
ap = &globals->adapter;
|
|
if(ap->power == AC) {
|
|
printf("On AC Power");
|
|
for(i = 0; i < globals->battery_count; i++) {
|
|
binfo = &batteries[i];
|
|
if(binfo->present && (binfo->charge_state == CHARGE)) {
|
|
printf("; Battery %s charging", binfo->name);
|
|
printf(", currently at %2d%%", binfo->percentage);
|
|
if(binfo->charge_time >= 0)
|
|
printf(", %2d:%02d remaining",
|
|
binfo->charge_time/60,
|
|
binfo->charge_time%60);
|
|
}
|
|
}
|
|
printf("\n");
|
|
} else if(ap->power == BATT) {
|
|
printf("On Battery");
|
|
for(i = 0; i < globals->battery_count; i++) {
|
|
binfo = &batteries[i];
|
|
if(binfo->present && (binfo->percentage >= 0))
|
|
printf(", Battery %s at %d%%", binfo->name,
|
|
binfo->percentage);
|
|
}
|
|
if(globals->rtime >= 0)
|
|
printf("; %d:%02d remaining", globals->rtime/60,
|
|
globals->rtime%60);
|
|
printf("\n");
|
|
}
|
|
return;
|
|
}
|
|
|
|
battery_t *switch_battery(global_t *globals, int battno)
|
|
{
|
|
globals->binfo = &batteries[battno];
|
|
pinfo("changing to monitor battery %s\n", globals->binfo->name);
|
|
set_batt_id_area(battno);
|
|
dockapp->update = 1;
|
|
|
|
return globals->binfo;
|
|
}
|
|
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
char *display = NULL;
|
|
int sample_count = 0;
|
|
int batt_reinit, ac_reinit;
|
|
int batt_count = 0;
|
|
int ac_count = 0;
|
|
int cli = 0, samples = 1, critical = 10;
|
|
int samplerate = 20;
|
|
int scroll_count = 0;
|
|
enum rtime_mode rt_mode = RT_RATE;
|
|
int rt_forced = 0;
|
|
battery_t *binfo = NULL;
|
|
global_t *globals;
|
|
|
|
fd_set fds;
|
|
struct timeval tv_rate;
|
|
struct timeval tv = {0, 0};
|
|
|
|
DAProgramOption options[] = {
|
|
{"-r", "--no-scroll", "disable scrolling message", DONone, False, {NULL}},
|
|
{"-n", "--no-blink", "disable blinking of various UI elements", DONone, False, {NULL}},
|
|
{"-x", "--cmdline", "run in command line mode", DONone, False, {NULL}},
|
|
{"-f", "--force-capacity-mode", "force the use of capacity mode for calculating time remaining", DONone, False, {NULL}},
|
|
{"-d", "--display", "display or remote display", DOString, False, {&display}},
|
|
{"-c", "--critical", "set critical low alarm at <number> percent\n (default: 10 percent)", DONatural, False, {&critical}},
|
|
{"-m", "--battery", "battery number to monitor", DONatural, False, {&battery_no}},
|
|
{"-s", "--sample-rate", "number of times per minute to sample battery information\n default 20 (once every three seconds)", DONatural, False, {&samplerate}},
|
|
{"-V", "--verbosity", "Set verbosity", DONatural, False, {&verbosity}},
|
|
{"-a", "--samples", "number of samples to average over (cli mode only)", DONatural, False, {&samples}},
|
|
};
|
|
|
|
dockapp = calloc(1, sizeof(struct dockapp));
|
|
globals = calloc(1, sizeof(global_t));
|
|
|
|
dockapp->blink = 1;
|
|
dockapp->bell = 0;
|
|
dockapp->scroll = 1;
|
|
dockapp->scroll_reset = 0;
|
|
globals->crit_level = 10;
|
|
battery_no = 1;
|
|
|
|
/* after this many samples, we reinit the battery and AC adapter
|
|
* information.
|
|
* XXX: make these configurable . . . */
|
|
batt_reinit = 100;
|
|
ac_reinit = 1000;
|
|
|
|
/* this needs to be up here because we need to know what batteries
|
|
* are available /before/ we can decide if the battery we want to
|
|
* monitor is available. */
|
|
/* parse command-line options */
|
|
DAParseArguments(argc, argv, options, 10,
|
|
"A battery monitor dockapp for ACPI based systems",
|
|
VERSION);
|
|
|
|
if (options[0].used)
|
|
dockapp->scroll = 0;
|
|
if (options[1].used)
|
|
dockapp->blink = 0;
|
|
if (options[2].used)
|
|
cli = 1;
|
|
if (options[3].used) {
|
|
rt_mode = RT_CAP;
|
|
rt_forced = 1;
|
|
}
|
|
|
|
if (samplerate == 0) samplerate = 1;
|
|
if (samplerate > 600) samplerate = 600;
|
|
|
|
/* convert to number of base periods */
|
|
samplerate = ((60 * 1000000) / samplerate) / BASE_PERIOD;
|
|
|
|
if (!dockapp->scroll) {
|
|
if (!dockapp->blink) {
|
|
/* Adapt the period to the sample rate */
|
|
tv_rate.tv_usec = samplerate * BASE_PERIOD;
|
|
|
|
tv_rate.tv_sec = tv_rate.tv_usec / 1000000;
|
|
tv_rate.tv_usec = tv_rate.tv_usec - (tv_rate.tv_sec * 1000000);
|
|
|
|
dockapp->period_length = samplerate;
|
|
} else {
|
|
/* blinking is every second */
|
|
tv_rate.tv_sec = 1; /* BASE_PERIOD * 10 = 1 sec */
|
|
tv_rate.tv_usec = 0;
|
|
|
|
dockapp->period_length = 10;
|
|
}
|
|
} else {
|
|
/* scrolling is every BASE_PERIOD (100 ms) */
|
|
tv_rate.tv_sec = 0;
|
|
tv_rate.tv_usec = BASE_PERIOD;
|
|
|
|
dockapp->period_length = 1;
|
|
}
|
|
|
|
if (critical > 100) {
|
|
fprintf(stderr, "Please use values between 0 and 100%%\n");
|
|
fprintf(stderr, "Using default value of 10%%\n");
|
|
critical = 10;
|
|
}
|
|
globals->crit_level = critical;
|
|
|
|
if (battery_no >= MAXBATT) {
|
|
fprintf(stderr, "Please specify a battery number below %d\n", MAXBATT);
|
|
return 1;
|
|
}
|
|
pinfo("Monitoring battery %d\n", battery_no);
|
|
|
|
if (power_init(globals))
|
|
/* power_init functions handle printing error messages */
|
|
exit(1);
|
|
|
|
globals->rt_mode = rt_mode;
|
|
globals->rt_forced = rt_forced;
|
|
|
|
if (battery_no > globals->battery_count) {
|
|
pinfo("Battery %d not available for monitoring.\n", battery_no);
|
|
}
|
|
|
|
/* check for cli mode */
|
|
if (cli) {
|
|
cli_wmacpi(globals, samples);
|
|
exit(0);
|
|
}
|
|
/* check to see if we've got a valid DISPLAY env variable, as a simple check to see if
|
|
* we're running under X */
|
|
if (!getenv("DISPLAY")) {
|
|
pdebug("Not running under X - using cli mode\n");
|
|
cli_wmacpi(globals, samples);
|
|
exit(0);
|
|
}
|
|
|
|
battery_no--;
|
|
|
|
/* make new dockapp window */
|
|
/* Don't even /think/ of asking me why, but if I set the window name to
|
|
* "acpi", the app refuses to dock properly - it's just plain /weird/.
|
|
* So, wmacpi it is . . . */
|
|
new_window(display, "wmacpi", argc, argv);
|
|
|
|
/* get initial statistics */
|
|
acquire_all_info(globals);
|
|
|
|
if (globals->battery_count > 0) {
|
|
binfo = &batteries[battery_no];
|
|
globals->binfo = binfo;
|
|
set_batt_id_area(battery_no);
|
|
pinfo("monitoring battery %s\n", binfo->name);
|
|
}
|
|
|
|
clear_time_display();
|
|
set_power_panel(globals);
|
|
set_message(globals);
|
|
|
|
/* main loop */
|
|
while (1) {
|
|
Atom atom;
|
|
Atom wmdelwin;
|
|
XEvent event;
|
|
while (XPending(dockapp->display)) {
|
|
XNextEvent(dockapp->display, &event);
|
|
switch (event.type) {
|
|
case Expose:
|
|
/* update */
|
|
dockapp->update = 1;
|
|
while (XCheckTypedEvent(dockapp->display, Expose, &event));
|
|
redraw_window();
|
|
break;
|
|
case DestroyNotify:
|
|
XCloseDisplay(dockapp->display);
|
|
exit(0);
|
|
break;
|
|
case ButtonPress:
|
|
break;
|
|
case ButtonRelease:
|
|
if (globals->battery_count == 0)
|
|
break;
|
|
|
|
/* cycle through the known batteries. */
|
|
battery_no++;
|
|
battery_no = battery_no % globals->battery_count;
|
|
|
|
binfo = switch_battery(globals, battery_no);
|
|
break;
|
|
case ClientMessage:
|
|
/* what /is/ this crap?
|
|
* Turns out that libdockapp adds the WM_DELETE_WINDOW atom to
|
|
* the WM_PROTOCOLS property for the window, which means that
|
|
* rather than get a simple DestroyNotify message, we get a
|
|
* nice little message from the WM saying "hey, can you delete
|
|
* yourself, pretty please?". So, when running as a window
|
|
* rather than an icon, we're impossible to kill in a friendly
|
|
* manner, because we're expecting to die from a DestroyNotify
|
|
* and thus blithely ignoring the WM knocking on our window
|
|
* border . . .
|
|
*
|
|
* This simply checks for that scenario - it may fail oddly if
|
|
* something else comes to us via a WM_PROTOCOLS ClientMessage
|
|
* event, but I suspect it's not going to be an issue. */
|
|
wmdelwin = XInternAtom(dockapp->display, "WM_DELETE_WINDOW", 1);
|
|
atom = event.xclient.data.l[0];
|
|
if (atom == wmdelwin) {
|
|
XCloseDisplay(dockapp->display);
|
|
exit(0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* XXX: some laptops have problems with sampling the battery
|
|
* regularly - apparently, the BIOS disables interrupts while
|
|
* reading from the battery, which is generally on a slow bus
|
|
* and is a slow device, so you get significant periods without
|
|
* interrupts. This causes interactivity to suffer . . .
|
|
*
|
|
* So, the workaround/fix for this is to sample at a much
|
|
* lower rate than we may update/refresh/expose/whatever. The
|
|
* user specifies how many times they want us to sample per
|
|
* minute; we use select() on our X events fd to wake up when
|
|
* there's some display work to be done, with the timeout set
|
|
* to whatever the time between samples is. When we hit our
|
|
* select() timeout we update our samples, otherwise we update
|
|
* the display.
|
|
*
|
|
* Note that this has a wrinkle when blinking and/or scrolling
|
|
* is enabled, since we need to update the display more
|
|
* frequently than we sample (most likely). In that case we
|
|
* set the timeout based on the display update cycle. */
|
|
|
|
/* have we completed our timeout, or were we woken up early? */
|
|
if ((tv.tv_sec != 0) || (tv.tv_usec != 0))
|
|
goto win_update;
|
|
|
|
tv = tv_rate;
|
|
|
|
sample_count += dockapp->period_length;
|
|
|
|
if (sample_count >= samplerate) {
|
|
if (globals->battery_count == 0) {
|
|
batt_count = 0;
|
|
|
|
reinit_batteries(globals);
|
|
|
|
/* battery appeared */
|
|
if (globals->battery_count > 0) {
|
|
if (battery_no > globals->battery_count)
|
|
battery_no = 0;
|
|
|
|
binfo = switch_battery(globals, battery_no);
|
|
}
|
|
}
|
|
|
|
acquire_all_info(globals);
|
|
|
|
/* we need to be able to reinitialise batteries and adapters, because
|
|
* they change - you can hotplug batteries on most laptops these days
|
|
* and who knows what kind of shit will be happening soon . . . */
|
|
if (batt_count++ >= batt_reinit) {
|
|
if(reinit_batteries(globals))
|
|
pfatal("Oh my god, the batteries are gone!\n");
|
|
batt_count = 0;
|
|
}
|
|
|
|
if (ac_count++ >= ac_reinit) {
|
|
if(reinit_ac_adapters(globals))
|
|
pfatal("What happened to our AC adapters?!?\n");
|
|
ac_count = 0;
|
|
}
|
|
sample_count = 0;
|
|
}
|
|
|
|
if (scroll_count++ >= scroll_reset) {
|
|
reset_scroll();
|
|
scroll_count = 0;
|
|
}
|
|
|
|
/* The old code had some kind of weird crap with timers and the like.
|
|
* As far as I can tell, it's meaningless - the time we want to display
|
|
* is the time calculated from the remaining capacity, as per the
|
|
* ACPI spec. The only thing I'd change is the handling of a charging
|
|
* state: my best guess, based on the behaviour I'm seeing with my
|
|
* Lifebook, is that the present rate value when charging is the rate
|
|
* at which the batteries are being charged, which would mean I'd just
|
|
* need to reverse the rtime calculation to be able to work out how
|
|
* much time remained until the batteries were fully charged . . .
|
|
* That would be rather useful, though given it would vary rather a lot
|
|
* it seems likely that it'd be little more than a rough guesstimate. */
|
|
set_time_display(globals);
|
|
set_power_panel(globals);
|
|
set_message(globals);
|
|
|
|
if (globals->battery_count == 0) {
|
|
clear_percentage();
|
|
clear_batt_id_area();
|
|
} else
|
|
display_percentage(binfo->percentage);
|
|
|
|
scroll_text();
|
|
|
|
win_update:
|
|
/* redraw_window, if anything changed */
|
|
redraw_window();
|
|
|
|
FD_ZERO(&fds);
|
|
FD_SET(dockapp->x_fd, &fds);
|
|
select(FD_SETSIZE, &fds, NULL, NULL, &tv);
|
|
}
|
|
return 0;
|
|
}
|