3 * This is a patched xapm working with acpi (which does not support
6 * xapm was written and (C) by Rickard E. Faith (r.faith@ieee.org)
8 * 2004/06/10 Patch for acpi by Francois Fleuret (francois@fleuret.org)
9 * 2004/07/20 Patch from Bryan Cardillo (dillo@seas.upenn.edu)
13 * gcc -DNARROWPROTO -o xacpi -L /usr/X11R6/lib/ -lX11 -lXaw xacpi.c
15 * This program is free software; you can redistribute it and/or modify it
16 * under the terms of the GNU General Public License as published by the
17 * Free Software Foundation; either version 2, or (at your option) any
20 * This program is distributed in the hope that it will be useful, but
21 * WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
23 * General Public License for more details.
25 * You should have received a copy of the GNU General Public License along
26 * with this program; if not, write to the Free Software Foundation, Inc.,
27 * 675 Mass Ave, Cambridge, MA 02139, USA.
34 #include <sys/types.h>
38 #include <X11/Intrinsic.h>
39 #include <X11/StringDefs.h>
40 #include <X11/cursorfont.h>
41 #include <X11/Xaw/Form.h>
42 #include <X11/Xaw/Command.h>
43 #include <X11/Xaw/Scrollbar.h>
45 /* The acpi file readings can fail sometime, but we will immediately
46 retry. We display ERR in the window if we fail more than that
49 #define NB_TOLERATED_FAILURES 1
51 #define XACPI_VERSION "1.3"
52 #define MIN_BAR_WIDTH 32
54 #define DEFAULT_ACPI_DIR "/proc/acpi"
55 #define DEFAULT_CPUFREQ_DIR "/sys/devices/system/cpu/cpu0/cpufreq"
57 #define BUFFER_LEN 1024
59 #define DEFAULT_DELAY 5
60 #define DEFAULT_DATE_FORMAT "%a %b %e %H:%M:%S %Z %Y"
62 typedef struct _instance_variables {
63 Pixel highColor, lowColor, criticalColor, chargingColor;
65 int highValue, lowValue, criticalTemp;
68 Boolean displayPercent, displayPercentAuto;
69 Boolean showBar, showTemperature;
76 } instance_variable_rec;
79 int design_capacity, last_full_capacity, remaining_capacity;
83 int battery_percentage, battery_time;
88 static XtAppContext app_context;
89 static Widget scrollbar;
90 static Widget topLevel;
93 static Widget temperature;
94 static Widget cpufreq;
96 static XtIntervalId timerId;
97 static instance_variable_rec iv;
100 static int nb_failures;
102 char *get_field_name(char *buffer, char *r) {
103 while(*r && *r != ':') *(buffer++) = *(r++);
104 while(*r == ' ' || *r == ':') r++;
109 int get_int(char *r) {
110 char tmp[BUFFER_LEN];
112 while(*r >= '0' && *r <= '9') *(t++) = *(r++);
117 char *goto_next_line(char *r) {
118 while(*r && *r != '\n') r++;
119 while(*r == '\n') r++;
120 if(*r) return r; else return 0;
123 /* We make the assumption that the file is shorter than BUFFER_LEN
126 void read_proc_file(char *path, char *subdir, char *filename, char *content) {
127 char name[BUFFER_LEN];
131 snprintf(name, BUFFER_LEN, "%s/%s/%s", path, subdir, filename);
133 file = fopen(name, "r");
136 printf("reading of [%s]\n", name);
138 len = fread(content, 1, BUFFER_LEN - 1, file);
142 printf("opening of [%s] failed\n", name);
150 printf("reading of [%s] failed\n", name);
153 else content[len] = '\0';
156 void check_acpi(char *acpi_dir) {
159 directory= opendir(acpi_dir);
160 if(directory) closedir(directory);
161 else err(1, "%s", acpi_dir);
164 void acpi_read(char *acpi_dir, char *cpu_dir, struct acpi_info *acpi) {
166 struct dirent *dir_entry;
167 char content[BUFFER_LEN], word[BUFFER_LEN], dir_name[BUFFER_LEN];
171 memset(acpi, 0, sizeof(*acpi));
173 /* Read the battery information */
175 snprintf(dir_name, BUFFER_LEN, "%s/battery", acpi_dir);
176 directory = opendir(dir_name);
177 if (!(directory)) err(1, "%s", dir_name);
179 while((dir_entry = readdir(directory))) {
180 if(dir_entry->d_name[0] != '.') {
183 read_proc_file(dir_name, dir_entry->d_name, "info", content);
187 s = get_field_name(word, s);
189 if(strcmp(word, "ERROR") == 0) {
191 printf("Read \"ERROR\"\n");
196 else if(strcmp(word, "design capacity") == 0 && s) acpi->design_capacity += get_int(s);
197 else if(strcmp(word, "last full capacity") == 0 && s) acpi->last_full_capacity += get_int(s);
198 s = goto_next_line(s);
201 read_proc_file(dir_name, dir_entry->d_name, "state", content);
204 s = get_field_name(word, s);
205 if(strcmp(word, "ERROR") == 0) {
207 printf("Read \"ERROR\"\n");
212 else if(strcmp(word, "present rate") == 0 && s) acpi->present_rate += get_int(s);
213 else if(strcmp(word, "remaining capacity") == 0 && s) acpi->remaining_capacity += get_int(s);
214 else if(strcmp(word, "charging state") == 0 && s) {
215 if(strncmp(s, "charging", 8) == 0) acpi->charging = 1;
216 else if(strncmp(s, "charged", 7) == 0) acpi->charging = 0;
217 else if(strncmp(s, "discharging", 11) == 0) acpi->charging = -1;
219 s = goto_next_line(s);
226 /* Read the AC line information */
228 snprintf(dir_name, BUFFER_LEN, "%s/ac_adapter", acpi_dir);
229 directory = opendir(dir_name);
230 if (!(directory)) err(1, "%s", dir_name);
232 while((dir_entry = readdir(directory))) {
233 if(dir_entry->d_name[0] != '.') {
235 read_proc_file(dir_name, dir_entry->d_name, "state", content);
238 s = get_field_name(word, s);
239 if(strcmp(word, "ERROR") == 0) {
241 printf("Read \"ERROR\"\n");
246 else if(strcmp(word, "state") == 0 && s)
247 acpi->ac_on_line = (strncmp(s, "on-line", 7) == 0);
248 s = goto_next_line(s);
255 /* When required, read the temperature information */
258 snprintf(dir_name, BUFFER_LEN, "%s/thermal_zone", acpi_dir);
259 directory = opendir(dir_name);
260 if (!(directory)) err(1, "%s", dir_name);
262 acpi->temperature = -1;
264 while((dir_entry = readdir(directory))) {
265 if(dir_entry->d_name[0] != '.') {
267 read_proc_file(dir_name, dir_entry->d_name, "temperature", content);
270 s = get_field_name(word, s);
271 if(strcmp(word, "ERROR") == 0) {
273 printf("Read \"ERROR\"\n");
278 else if(strcmp(word, "temperature") == 0 && s) {
279 int temp = get_int(s);
280 if(temp > acpi->temperature) acpi->temperature = temp;
282 s = goto_next_line(s);
290 /* When required, read the frequency information */
293 read_proc_file(cpu_dir, "", "scaling_cur_freq", content);
295 acpi->cpufreq = get_int(s)/1000;
298 if(error) nb_failures++; else nb_failures = 0;
300 if(nb_failures == 0) {
302 // Bryan Cardillo preferes to see the percentage of the design
303 // capacity. I think it's kind of depressive to see that the
304 // battery gets worst and worst ...
306 /* if (acpi->charging == 0) { */
307 /* acpi->battery_percentage = (acpi->remaining_capacity * 100) / acpi->design_capacity; */
309 acpi->battery_percentage = (acpi->remaining_capacity * 100) / acpi->last_full_capacity;
312 if(acpi->present_rate) {
313 if(acpi->charging > 0)
314 acpi->battery_time = ((acpi->last_full_capacity - acpi->remaining_capacity) * 3600) / acpi->present_rate;
315 else if(acpi->charging < 0)
316 acpi->battery_time = (acpi->remaining_capacity * 3600) / acpi->present_rate;
318 } else if(nb_failures > NB_TOLERATED_FAILURES) {
319 acpi->battery_time = -1;
323 void put_date(char *buf, char *format) {
326 strftime(buf, BUFFER_LEN, format, localtime(&t));
329 static void update(XtPointer client_data, XtIntervalId * id) {
330 struct acpi_info acpi;
333 static int current_bar_color = -1;
334 static int current_charging_color = -1;
335 static int current_temperature_color = -1;
336 static int blink_counter = 0;
338 static int lastPercentage = -1;
339 static int lastMinutes = -1;
340 static int lastDisplay = -1;
341 static int lastACStatus = -1;
342 static int lastTemperature = -1;
343 static int lastCpufreq = -1;
345 acpi_read(iv.acpiDir, iv.cpuFreqDir, &acpi);
347 if(nb_failures > NB_TOLERATED_FAILURES) {
349 XtVaSetValues(delay, XtNlabel, "ERR", NULL);
351 if(scrollbar && current_bar_color != iv.criticalColor) {
352 current_bar_color = iv.criticalColor;
353 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
354 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
357 /* All this will have to be refreshed */
364 lastTemperature = -1;
369 /* We refresh only if there were no error during updating */
371 else if(nb_failures == 0) {
373 if(iv.displayPercentAuto && acpi.ac_on_line != lastACStatus){
375 iv.displayPercent = acpi.ac_on_line;
378 if (iv.displayPercent) {
379 if (lastDisplay != iv.displayPercent
380 || acpi.battery_percentage != lastPercentage
381 || acpi.ac_on_line != lastACStatus) {
383 /* lastPercentage updated at end */
385 snprintf(buf, BUFFER_LEN, "%s%d%%", acpi.ac_on_line ? "L" : "B", acpi.battery_percentage);
386 XtVaSetValues(delay, XtNlabel, buf, NULL);
390 /* Negative value means we could not estimate it in acpi_read
391 because the charging speed was 0 */
393 if(acpi.battery_time < 0) {
394 snprintf(buf, BUFFER_LEN, "%s", acpi.ac_on_line ? "L" : "B???");
395 XtVaSetValues(delay, XtNlabel, buf, NULL);
399 int minutes = acpi.battery_time / 60;
400 if (lastDisplay != iv.displayPercent || lastMinutes != minutes
401 || acpi.ac_on_line != lastACStatus
403 lastMinutes = minutes;
404 snprintf(buf, BUFFER_LEN, "%s%lu:%02lu", acpi.ac_on_line ? "L" : "B", minutes/60, minutes%60);
405 XtVaSetValues(delay, XtNlabel, buf, NULL);
411 lastDisplay = iv.displayPercent;
412 lastACStatus = acpi.ac_on_line;
415 if (acpi.battery_percentage <= iv.lowValue) {
416 if (current_bar_color != iv.criticalColor) {
417 current_bar_color = iv.criticalColor;
418 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
419 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
421 } else if (acpi.battery_percentage <= iv.highValue) {
422 if (current_bar_color != iv.lowColor) {
423 current_bar_color = iv.lowColor;
424 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
425 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
428 if (current_bar_color != iv.highColor) {
429 current_bar_color = iv.highColor;
430 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
431 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
437 int degrees = (iv.fahrenheit) ?
438 32 + (acpi.temperature * 18) / 10 : acpi.temperature;
440 if (degrees != lastTemperature) {
442 snprintf(buf, BUFFER_LEN, "%dF", degrees);
444 snprintf(buf, BUFFER_LEN, "%dC", degrees);
446 XtVaSetValues(temperature, XtNlabel, buf, NULL);
449 if(degrees >= iv.criticalTemp) {
450 if (current_temperature_color != iv.criticalColor) {
451 current_temperature_color = iv.criticalColor;
452 XtVaSetValues(temperature, XtNforeground, current_temperature_color, NULL);
455 if (current_temperature_color != iv.foreground) {
456 current_temperature_color = iv.foreground;
457 XtVaSetValues(temperature, XtNforeground, current_temperature_color, NULL);
461 lastTemperature = degrees;
464 if(cpufreq && acpi.cpufreq != lastCpufreq) {
465 snprintf(buf, BUFFER_LEN, "%.1fGhz", ((float) acpi.cpufreq)/1000.0);
466 XtVaSetValues(cpufreq, XtNlabel, buf, NULL);
470 put_date(buf, iv.dateFormat);
471 XtVaSetValues(date, XtNlabel, buf, NULL);
474 if (acpi.charging > 0) {
475 if (current_charging_color != iv.chargingColor)
476 XtVaSetValues(delay, XtNforeground,
477 current_charging_color = iv.chargingColor, NULL);
479 if (acpi.battery_percentage < iv.lowValue && blink_counter++ % 2) {
480 if (current_charging_color != iv.criticalColor)
482 XtNforeground, current_charging_color = iv.criticalColor, NULL);
484 if (current_charging_color != iv.foreground)
486 XtNforeground, current_charging_color = iv.foreground, NULL);
490 if (scrollbar && acpi.battery_percentage != lastPercentage) {
491 XawScrollbarSetThumb(scrollbar, 0.0,
492 acpi.battery_percentage < 0 ? 0.0 : acpi.battery_percentage / 100.0);
493 lastPercentage = acpi.battery_percentage;
497 /* We come back in 0.5s if there was an error */
499 timerId = XtAppAddTimeOut(app_context,
500 ((nb_failures > 0 && nb_failures <= NB_TOLERATED_FAILURES)) ? 250 : (1000 * iv.delay + 500),
501 update, app_context);
504 static void press(Widget w, XtPointer client_data, XtPointer call_data){
506 iv.displayPercent = !iv.displayPercent;
507 } else if (w == temperature) {
508 iv.fahrenheit = !iv.fahrenheit;
510 XtRemoveTimeOut(timerId);
511 timerId = XtAppAddTimeOut(app_context, 0, update, app_context);
514 static XrmOptionDescRec options[] = {
515 {"-highcolor", "*highColor", XrmoptionSepArg, NULL},
516 {"-lowcolor", "*lowColor", XrmoptionSepArg, NULL},
517 {"-criticalcolor", "*criticalColor", XrmoptionSepArg, NULL},
518 {"-criticaltemperature", "*criticalTemperature", XrmoptionSepArg, NULL},
519 {"-chargingcolor", "*chargingColor", XrmoptionSepArg, NULL},
520 {"-highvalue", "*highValue", XrmoptionSepArg, NULL},
521 {"-lowvalue", "*lowValue", XrmoptionSepArg, NULL},
522 {"-delay", "*delay", XrmoptionSepArg, NULL},
523 {"-percent", "*percent", XrmoptionNoArg, (XtPointer) "true"},
524 {"-percentauto", "*percentAuto", XrmoptionNoArg, (XtPointer) "true"},
525 {"-showbar", "*showBar", XrmoptionNoArg, (XtPointer) "true"},
526 {"-showtemp", "*showTemperature", XrmoptionNoArg, (XtPointer) "true"},
527 {"-showcpufreq", "*showCpuFreq", XrmoptionNoArg, (XtPointer) "true"},
528 {"-showdate", "*showDate", XrmoptionNoArg, (XtPointer) "true"},
529 {"-fahrenheit", "*fahrenheit", XrmoptionNoArg, (XtPointer) "true"},
530 {"-dateformat", "*dateFormat", XrmoptionSepArg, NULL},
531 {"-acpidir", "*acpiDir", XrmoptionSepArg, NULL},
532 {"-cpufreqdir", "*cpuFreqDir", XrmoptionSepArg, NULL},
535 #define offset(field) XtOffsetOf( instance_variable_rec, field )
537 static XtResource resources[] = {
539 {"highColor", XtCForeground, XtRPixel, sizeof(Pixel),
540 offset(highColor), XtRString, "green"},
541 {"lowColor", XtCForeground, XtRPixel, sizeof(Pixel),
542 offset(lowColor), XtRString, "yellow"},
543 {"criticalColor", XtCForeground, XtRPixel, sizeof(Pixel),
544 offset(criticalColor), XtRString, "red"},
545 {"chargingColor", XtCForeground, XtRPixel, sizeof(Pixel),
546 offset(chargingColor), XtRString, "blue"},
548 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
549 offset(foreground), XtRString, XtDefaultForeground},
550 {"highValue", XtCValue, XtRInt, sizeof(int),
551 offset(highValue), XtRImmediate, (XtPointer) 50},
552 {"lowValue", XtCValue, XtRInt, sizeof(int),
553 offset(lowValue), XtRImmediate, (XtPointer) 10},
554 {"criticalTemperature", XtCValue, XtRInt, sizeof(int),
555 offset(criticalTemp), XtRImmediate, (XtPointer) 0},
556 {"geometry", XtCString, XtRString, sizeof(String),
557 offset(geometry), XtRString, (XtPointer) "170x10"},
558 {"delay", XtCValue, XtRInt, sizeof(int),
559 offset(delay), XtRImmediate, (XtPointer) DEFAULT_DELAY},
560 {"percent", XtCValue, XtRBoolean, sizeof(Boolean),
561 offset(displayPercent), XtRImmediate, (XtPointer) FALSE},
562 {"percentAuto", XtCValue, XtRBoolean, sizeof(Boolean),
563 offset(displayPercentAuto), XtRImmediate, (XtPointer) FALSE},
564 {"showBar", XtCValue, XtRBoolean, sizeof(Boolean),
565 offset(showBar), XtRImmediate, (XtPointer) FALSE},
566 {"showTemperature", XtCValue, XtRBoolean, sizeof(Boolean),
567 offset(showTemperature), XtRImmediate, (XtPointer) FALSE},
568 {"showCpuFreq", XtCValue, XtRBoolean, sizeof(Boolean),
569 offset(showCpufreq), XtRImmediate, (XtPointer) FALSE},
570 {"showDate", XtCValue, XtRBoolean, sizeof(Boolean),
571 offset(showDate), XtRImmediate, (XtPointer) FALSE},
572 {"fahrenheit", XtCValue, XtRBoolean, sizeof(Boolean),
573 offset(fahrenheit), XtRImmediate, (XtPointer) FALSE},
574 {"dateFormat", XtCString, XtRString, sizeof(String),
575 offset(dateFormat), XtRString, (XtPointer) DEFAULT_DATE_FORMAT},
576 {"acpiDir", XtCString, XtRString, sizeof(String),
577 offset(acpiDir), XtRString, (XtPointer) DEFAULT_ACPI_DIR},
578 {"cpuFreqDir", XtCString, XtRString, sizeof(String),
579 offset(cpuFreqDir), XtRString, (XtPointer) DEFAULT_CPUFREQ_DIR},
582 static void quit(Widget w, XtPointer client_data, XtPointer call_data){
586 static XtActionsRec main_actions[] = { {"Quit", (XtActionProc) quit},
589 static String main_translations = "<Message>WM_PROTOCOLS:Quit()\n";
591 int main(int argc, char **argv){
594 int x = 0, y = 0, height = 0, width = 0;
595 int current_width = 0, own_width;
597 Atom wm_protocols[1];
602 topLevel = XtVaAppInitialize(&app_context, "XAcpi",
603 options, XtNumber(options),
604 &argc, argv, NULL, NULL);
606 XtGetApplicationResources(topLevel,
612 if(iv.criticalTemp <= 0) {
613 if(iv.fahrenheit) iv.criticalTemp = 158;
614 else iv.criticalTemp = 70;
617 check_acpi(iv.acpiDir);
619 if (iv.delay < 1) iv.delay = DEFAULT_DELAY;
621 XParseGeometry(iv.geometry, &x, &y, &width, &height);
623 while ((c = getopt(argc, argv, "DVH")) != -1)
629 fprintf(stderr, "xacpi version " XACPI_VERSION ".\n");
630 fprintf(stderr, "$Id: xacpi.c,v 1.40 2004/12/03 07:52:30 fleuret Exp $.\n");
637 " -highcolor <string: color>\n"
638 " -lowcolor <string: color>\n"
639 " -criticalcolor <string: color>\n"
640 " -chargingcolor <string: color>\n"
641 " -highvalue <int: percentage>\n"
642 " -lowvalue <int: percentage>\n"
643 " -delay <int: seconds>\n"
650 " -dateformat <string: format>\n"
652 " -acpidir <string: path>\n"
653 " -cpufreqdir <string: path>\n"
655 "See man xacpi for more detailed information.\n");
660 form = XtVaCreateManagedWidget("form",
661 formWidgetClass, topLevel,
662 XtNorientation, XtorientHorizontal,
664 XtNdefaultDistance, 0,
670 date = XtVaCreateManagedWidget("date",
671 labelWidgetClass, form,
672 XtNinternalHeight, 0,
679 char buf[BUFFER_LEN];
680 put_date(buf, iv.dateFormat);
682 XtVaGetValues(last, XtNfont, &fs, NULL);
683 own_width = ((strlen(buf) + 2) * fs->max_bounds.width);
684 XtVaSetValues(last, XtNwidth, own_width, NULL);
685 current_width += own_width;
688 /* Add the widget to display the remaining battery time */
690 delay = XtVaCreateManagedWidget("delay",
691 commandWidgetClass, form,
692 XtNleft, XtChainLeft,
693 XtNhighlightThickness, 0,
694 XtNinternalHeight, 0,
702 if(date) XtVaSetValues(delay, XtNfromHoriz, last, 0);
703 XtAddCallback(delay, XtNcallback, press, NULL);
706 XtVaGetValues(last, XtNfont, &fs, NULL);
707 own_width = 6 * fs->max_bounds.width;
708 XtVaSetValues(last, XtNwidth, own_width, NULL);
709 current_width += own_width;
711 /* Add the widget to display the colored bar */
715 scrollbar = XtVaCreateManagedWidget("scrollbar",
716 scrollbarWidgetClass, form,
719 XtNorientation, XtorientHorizontal,
722 XawScrollbarSetThumb(scrollbar, 0.0, 0.0);
724 XtVaSetValues(scrollbar,
725 XtNtranslations, XtParseTranslationTable(""),
729 } else scrollbar = 0;
731 /* Add the widget to display the temperature */
733 if(iv.showTemperature) {
734 temperature = XtVaCreateManagedWidget("temperature",
735 commandWidgetClass, form,
738 XtNhighlightThickness, 0,
739 XtNinternalHeight, 0,
742 XtNlabel, iv.fahrenheit ? "000F " : "00C ",
746 XtVaGetValues(last, XtNfont, &fs, NULL);
747 own_width = (iv. fahrenheit ? 5 : 4) * fs->max_bounds.width;
748 XtVaSetValues(last, XtNwidth, own_width, NULL);
749 current_width += own_width;
750 XtAddCallback(temperature, XtNcallback, press, NULL);
751 } else temperature = 0;
753 /* Add the widget to display the cpu frequency */
756 cpufreq = XtVaCreateManagedWidget("cpufreq",
757 labelWidgetClass, form,
760 XtNinternalHeight, 0,
767 XtVaGetValues(last, XtNfont, &fs, NULL);
768 own_width = 7 * fs->max_bounds.width;
769 XtVaSetValues(last, XtNwidth, own_width, NULL);
770 current_width += own_width;
773 /* Changed the bar length to the unused width */
776 if(width > current_width + MIN_BAR_WIDTH)
777 XtVaSetValues(scrollbar, XtNwidth, width - current_width, 0);
779 XtVaSetValues(scrollbar, XtNwidth, MIN_BAR_WIDTH, 0);
783 XtRealizeWidget(topLevel);
785 /* Add code to handle WM_DELETE_WINDOW cleanly. */
787 XtAppAddActions(app_context, main_actions, XtNumber(main_actions));
788 XtOverrideTranslations(topLevel, XtParseTranslationTable(main_translations));
790 = XInternAtom(XtDisplay(topLevel), "WM_DELETE_WINDOW", False);
791 XSetWMProtocols(XtDisplay(topLevel), XtWindow(topLevel), wm_protocols, 1);
793 timerId = XtAppAddTimeOut(app_context, 0, update, app_context);
794 XtAppMainLoop(app_context);