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@epfl.ch)
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 * $Id: xacpi.c,v 1.40 2004/12/03 07:52:30 fleuret Exp $
27 * You should have received a copy of the GNU General Public License along
28 * with this program; if not, write to the Free Software Foundation, Inc.,
29 * 675 Mass Ave, Cambridge, MA 02139, USA.
36 #include <sys/types.h>
40 #include <X11/Intrinsic.h>
41 #include <X11/StringDefs.h>
42 #include <X11/cursorfont.h>
43 #include <X11/Xaw/Form.h>
44 #include <X11/Xaw/Command.h>
45 #include <X11/Xaw/Scrollbar.h>
47 /* The acpi file readings can fail sometime, but we will immediately
48 retry. We display ERR in the window if we fail more than that
51 #define NB_TOLERATED_FAILURES 1
53 #define XACPI_VERSION "1.3"
54 #define MIN_BAR_WIDTH 32
56 #define DEFAULT_ACPI_DIR "/proc/acpi"
57 #define DEFAULT_CPUFREQ_DIR "/sys/devices/system/cpu/cpu0/cpufreq"
59 #define BUFFER_LEN 1024
61 #define DEFAULT_DELAY 5
62 #define DEFAULT_DATE_FORMAT "%a %b %e %H:%M:%S %Z %Y"
64 typedef struct _instance_variables {
65 Pixel highColor, lowColor, criticalColor, chargingColor;
67 int highValue, lowValue, criticalTemp;
70 Boolean displayPercent, displayPercentAuto;
71 Boolean showBar, showTemperature;
78 } instance_variable_rec;
81 int design_capacity, last_full_capacity, remaining_capacity;
85 int battery_percentage, battery_time;
90 static XtAppContext app_context;
91 static Widget scrollbar;
92 static Widget topLevel;
95 static Widget temperature;
96 static Widget cpufreq;
98 static XtIntervalId timerId;
99 static instance_variable_rec iv;
102 static int nb_failures;
104 char *get_field_name(char *buffer, char *r) {
105 while(*r && *r != ':') *(buffer++) = *(r++);
106 while(*r == ' ' || *r == ':') r++;
111 int get_int(char *r) {
112 char tmp[BUFFER_LEN];
114 while(*r >= '0' && *r <= '9') *(t++) = *(r++);
119 char *goto_next_line(char *r) {
120 while(*r && *r != '\n') r++;
121 while(*r == '\n') r++;
122 if(*r) return r; else return 0;
125 /* We make the assumption that the file is shorter than BUFFER_LEN
128 void read_proc_file(char *path, char *subdir, char *filename, char *content) {
129 char name[BUFFER_LEN];
133 snprintf(name, BUFFER_LEN, "%s/%s/%s", path, subdir, filename);
135 file = fopen(name, "r");
138 printf("reading of [%s]\n", name);
140 len = fread(content, 1, BUFFER_LEN - 1, file);
144 printf("opening of [%s] failed\n", name);
152 printf("reading of [%s] failed\n", name);
155 else content[len] = '\0';
158 void check_acpi(char *acpi_dir) {
161 directory= opendir(acpi_dir);
162 if(directory) closedir(directory);
163 else err(1, "%s", acpi_dir);
166 void acpi_read(char *acpi_dir, char *cpu_dir, struct acpi_info *acpi) {
168 struct dirent *dir_entry;
169 char content[BUFFER_LEN], word[BUFFER_LEN], dir_name[BUFFER_LEN];
173 memset(acpi, 0, sizeof(*acpi));
175 /* Read the battery information */
177 snprintf(dir_name, BUFFER_LEN, "%s/battery", acpi_dir);
178 directory = opendir(dir_name);
179 if (!(directory)) err(1, "%s", dir_name);
181 while((dir_entry = readdir(directory))) {
182 if(dir_entry->d_name[0] != '.') {
185 read_proc_file(dir_name, dir_entry->d_name, "info", content);
189 s = get_field_name(word, s);
191 if(strcmp(word, "ERROR") == 0) {
193 printf("Read \"ERROR\"\n");
198 else if(strcmp(word, "design capacity") == 0 && s) acpi->design_capacity += get_int(s);
199 else if(strcmp(word, "last full capacity") == 0 && s) acpi->last_full_capacity += get_int(s);
200 s = goto_next_line(s);
203 read_proc_file(dir_name, dir_entry->d_name, "state", content);
206 s = get_field_name(word, s);
207 if(strcmp(word, "ERROR") == 0) {
209 printf("Read \"ERROR\"\n");
214 else if(strcmp(word, "present rate") == 0 && s) acpi->present_rate += get_int(s);
215 else if(strcmp(word, "remaining capacity") == 0 && s) acpi->remaining_capacity += get_int(s);
216 else if(strcmp(word, "charging state") == 0 && s) {
217 if(strncmp(s, "charging", 8) == 0) acpi->charging = 1;
218 else if(strncmp(s, "charged", 7) == 0) acpi->charging = 0;
219 else if(strncmp(s, "discharging", 11) == 0) acpi->charging = -1;
221 s = goto_next_line(s);
228 /* Read the AC line information */
230 snprintf(dir_name, BUFFER_LEN, "%s/ac_adapter", acpi_dir);
231 directory = opendir(dir_name);
232 if (!(directory)) err(1, "%s", dir_name);
234 while((dir_entry = readdir(directory))) {
235 if(dir_entry->d_name[0] != '.') {
237 read_proc_file(dir_name, dir_entry->d_name, "state", content);
240 s = get_field_name(word, s);
241 if(strcmp(word, "ERROR") == 0) {
243 printf("Read \"ERROR\"\n");
248 else if(strcmp(word, "state") == 0 && s)
249 acpi->ac_on_line = (strncmp(s, "on-line", 7) == 0);
250 s = goto_next_line(s);
257 /* When required, read the temperature information */
260 snprintf(dir_name, BUFFER_LEN, "%s/thermal_zone", acpi_dir);
261 directory = opendir(dir_name);
262 if (!(directory)) err(1, "%s", dir_name);
264 acpi->temperature = -1;
266 while((dir_entry = readdir(directory))) {
267 if(dir_entry->d_name[0] != '.') {
269 read_proc_file(dir_name, dir_entry->d_name, "temperature", content);
272 s = get_field_name(word, s);
273 if(strcmp(word, "ERROR") == 0) {
275 printf("Read \"ERROR\"\n");
280 else if(strcmp(word, "temperature") == 0 && s) {
281 int temp = get_int(s);
282 if(temp > acpi->temperature) acpi->temperature = temp;
284 s = goto_next_line(s);
292 /* When required, read the frequency information */
295 read_proc_file(cpu_dir, "", "scaling_cur_freq", content);
297 acpi->cpufreq = get_int(s)/1000;
300 if(error) nb_failures++; else nb_failures = 0;
302 if(nb_failures == 0) {
304 // Bryan Cardillo preferes to see the percentage of the design
305 // capacity. I think it's kind of depressive to see that the
306 // battery gets worst and worst ...
308 /* if (acpi->charging == 0) { */
309 /* acpi->battery_percentage = (acpi->remaining_capacity * 100) / acpi->design_capacity; */
311 acpi->battery_percentage = (acpi->remaining_capacity * 100) / acpi->last_full_capacity;
314 if(acpi->present_rate) {
315 if(acpi->charging > 0)
316 acpi->battery_time = ((acpi->last_full_capacity - acpi->remaining_capacity) * 3600) / acpi->present_rate;
317 else if(acpi->charging < 0)
318 acpi->battery_time = (acpi->remaining_capacity * 3600) / acpi->present_rate;
320 } else if(nb_failures > NB_TOLERATED_FAILURES) {
321 acpi->battery_time = -1;
325 void put_date(char *buf, char *format) {
328 strftime(buf, BUFFER_LEN, format, localtime(&t));
331 static void update(XtPointer client_data, XtIntervalId * id) {
332 struct acpi_info acpi;
335 static int current_bar_color = -1;
336 static int current_charging_color = -1;
337 static int current_temperature_color = -1;
338 static int blink_counter = 0;
340 static int lastPercentage = -1;
341 static int lastMinutes = -1;
342 static int lastDisplay = -1;
343 static int lastACStatus = -1;
344 static int lastTemperature = -1;
345 static int lastCpufreq = -1;
347 acpi_read(iv.acpiDir, iv.cpuFreqDir, &acpi);
349 if(nb_failures > NB_TOLERATED_FAILURES) {
351 XtVaSetValues(delay, XtNlabel, "ERR", NULL);
353 if(scrollbar && current_bar_color != iv.criticalColor) {
354 current_bar_color = iv.criticalColor;
355 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
356 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
359 /* All this will have to be refreshed */
366 lastTemperature = -1;
371 /* We refresh only if there were no error during updating */
373 else if(nb_failures == 0) {
375 if(iv.displayPercentAuto && acpi.ac_on_line != lastACStatus){
377 iv.displayPercent = acpi.ac_on_line;
380 if (iv.displayPercent) {
381 if (lastDisplay != iv.displayPercent
382 || acpi.battery_percentage != lastPercentage
383 || acpi.ac_on_line != lastACStatus) {
385 /* lastPercentage updated at end */
387 snprintf(buf, BUFFER_LEN, "%s%d%%", acpi.ac_on_line ? "L" : "B", acpi.battery_percentage);
388 XtVaSetValues(delay, XtNlabel, buf, NULL);
392 /* Negative value means we could not estimate it in acpi_read
393 because the charging speed was 0 */
395 if(acpi.battery_time < 0) {
396 snprintf(buf, BUFFER_LEN, "%s", acpi.ac_on_line ? "L" : "B???");
397 XtVaSetValues(delay, XtNlabel, buf, NULL);
401 int minutes = acpi.battery_time / 60;
402 if (lastDisplay != iv.displayPercent || lastMinutes != minutes
403 || acpi.ac_on_line != lastACStatus
405 lastMinutes = minutes;
406 snprintf(buf, BUFFER_LEN, "%s%lu:%02lu", acpi.ac_on_line ? "L" : "B", minutes/60, minutes%60);
407 XtVaSetValues(delay, XtNlabel, buf, NULL);
413 lastDisplay = iv.displayPercent;
414 lastACStatus = acpi.ac_on_line;
417 if (acpi.battery_percentage <= iv.lowValue) {
418 if (current_bar_color != iv.criticalColor) {
419 current_bar_color = iv.criticalColor;
420 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
421 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
423 } else if (acpi.battery_percentage <= iv.highValue) {
424 if (current_bar_color != iv.lowColor) {
425 current_bar_color = iv.lowColor;
426 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
427 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
430 if (current_bar_color != iv.highColor) {
431 current_bar_color = iv.highColor;
432 XtVaSetValues(scrollbar, XtNforeground, current_bar_color, NULL);
433 XtVaSetValues(scrollbar, XtNborderColor, current_bar_color, NULL);
439 int degrees = (iv.fahrenheit) ?
440 32 + (acpi.temperature * 18) / 10 : acpi.temperature;
442 if (degrees != lastTemperature) {
444 snprintf(buf, BUFFER_LEN, "%dF", degrees);
446 snprintf(buf, BUFFER_LEN, "%dC", degrees);
448 XtVaSetValues(temperature, XtNlabel, buf, NULL);
451 if(degrees >= iv.criticalTemp) {
452 if (current_temperature_color != iv.criticalColor) {
453 current_temperature_color = iv.criticalColor;
454 XtVaSetValues(temperature, XtNforeground, current_temperature_color, NULL);
457 if (current_temperature_color != iv.foreground) {
458 current_temperature_color = iv.foreground;
459 XtVaSetValues(temperature, XtNforeground, current_temperature_color, NULL);
463 lastTemperature = degrees;
466 if(cpufreq && acpi.cpufreq != lastCpufreq) {
467 snprintf(buf, BUFFER_LEN, "%.1fGhz", ((float) acpi.cpufreq)/1000.0);
468 XtVaSetValues(cpufreq, XtNlabel, buf, NULL);
472 put_date(buf, iv.dateFormat);
473 XtVaSetValues(date, XtNlabel, buf, NULL);
476 if (acpi.charging > 0) {
477 if (current_charging_color != iv.chargingColor)
478 XtVaSetValues(delay, XtNforeground,
479 current_charging_color = iv.chargingColor, NULL);
481 if (acpi.battery_percentage < iv.lowValue && blink_counter++ % 2) {
482 if (current_charging_color != iv.criticalColor)
484 XtNforeground, current_charging_color = iv.criticalColor, NULL);
486 if (current_charging_color != iv.foreground)
488 XtNforeground, current_charging_color = iv.foreground, NULL);
492 if (scrollbar && acpi.battery_percentage != lastPercentage) {
493 XawScrollbarSetThumb(scrollbar, 0.0,
494 acpi.battery_percentage < 0 ? 0.0 : acpi.battery_percentage / 100.0);
495 lastPercentage = acpi.battery_percentage;
499 /* We come back in 0.5s if there was an error */
501 timerId = XtAppAddTimeOut(app_context,
502 ((nb_failures > 0 && nb_failures <= NB_TOLERATED_FAILURES)) ? 250 : (1000 * iv.delay + 500),
503 update, app_context);
506 static void press(Widget w, XtPointer client_data, XtPointer call_data){
508 iv.displayPercent = !iv.displayPercent;
509 } else if (w == temperature) {
510 iv.fahrenheit = !iv.fahrenheit;
512 XtRemoveTimeOut(timerId);
513 timerId = XtAppAddTimeOut(app_context, 0, update, app_context);
516 static XrmOptionDescRec options[] = {
517 {"-highcolor", "*highColor", XrmoptionSepArg, NULL},
518 {"-lowcolor", "*lowColor", XrmoptionSepArg, NULL},
519 {"-criticalcolor", "*criticalColor", XrmoptionSepArg, NULL},
520 {"-criticaltemperature", "*criticalTemperature", XrmoptionSepArg, NULL},
521 {"-chargingcolor", "*chargingColor", XrmoptionSepArg, NULL},
522 {"-highvalue", "*highValue", XrmoptionSepArg, NULL},
523 {"-lowvalue", "*lowValue", XrmoptionSepArg, NULL},
524 {"-delay", "*delay", XrmoptionSepArg, NULL},
525 {"-percent", "*percent", XrmoptionNoArg, (XtPointer) "true"},
526 {"-percentauto", "*percentAuto", XrmoptionNoArg, (XtPointer) "true"},
527 {"-showbar", "*showBar", XrmoptionNoArg, (XtPointer) "true"},
528 {"-showtemp", "*showTemperature", XrmoptionNoArg, (XtPointer) "true"},
529 {"-showcpufreq", "*showCpuFreq", XrmoptionNoArg, (XtPointer) "true"},
530 {"-showdate", "*showDate", XrmoptionNoArg, (XtPointer) "true"},
531 {"-fahrenheit", "*fahrenheit", XrmoptionNoArg, (XtPointer) "true"},
532 {"-dateformat", "*dateFormat", XrmoptionSepArg, NULL},
533 {"-acpidir", "*acpiDir", XrmoptionSepArg, NULL},
534 {"-cpufreqdir", "*cpuFreqDir", XrmoptionSepArg, NULL},
537 #define offset(field) XtOffsetOf( instance_variable_rec, field )
539 static XtResource resources[] = {
541 {"highColor", XtCForeground, XtRPixel, sizeof(Pixel),
542 offset(highColor), XtRString, "green"},
543 {"lowColor", XtCForeground, XtRPixel, sizeof(Pixel),
544 offset(lowColor), XtRString, "yellow"},
545 {"criticalColor", XtCForeground, XtRPixel, sizeof(Pixel),
546 offset(criticalColor), XtRString, "red"},
547 {"chargingColor", XtCForeground, XtRPixel, sizeof(Pixel),
548 offset(chargingColor), XtRString, "blue"},
550 {XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
551 offset(foreground), XtRString, XtDefaultForeground},
552 {"highValue", XtCValue, XtRInt, sizeof(int),
553 offset(highValue), XtRImmediate, (XtPointer) 50},
554 {"lowValue", XtCValue, XtRInt, sizeof(int),
555 offset(lowValue), XtRImmediate, (XtPointer) 10},
556 {"criticalTemperature", XtCValue, XtRInt, sizeof(int),
557 offset(criticalTemp), XtRImmediate, (XtPointer) 0},
558 {"geometry", XtCString, XtRString, sizeof(String),
559 offset(geometry), XtRString, (XtPointer) "170x10"},
560 {"delay", XtCValue, XtRInt, sizeof(int),
561 offset(delay), XtRImmediate, (XtPointer) DEFAULT_DELAY},
562 {"percent", XtCValue, XtRBoolean, sizeof(Boolean),
563 offset(displayPercent), XtRImmediate, (XtPointer) FALSE},
564 {"percentAuto", XtCValue, XtRBoolean, sizeof(Boolean),
565 offset(displayPercentAuto), XtRImmediate, (XtPointer) FALSE},
566 {"showBar", XtCValue, XtRBoolean, sizeof(Boolean),
567 offset(showBar), XtRImmediate, (XtPointer) FALSE},
568 {"showTemperature", XtCValue, XtRBoolean, sizeof(Boolean),
569 offset(showTemperature), XtRImmediate, (XtPointer) FALSE},
570 {"showCpuFreq", XtCValue, XtRBoolean, sizeof(Boolean),
571 offset(showCpufreq), XtRImmediate, (XtPointer) FALSE},
572 {"showDate", XtCValue, XtRBoolean, sizeof(Boolean),
573 offset(showDate), XtRImmediate, (XtPointer) FALSE},
574 {"fahrenheit", XtCValue, XtRBoolean, sizeof(Boolean),
575 offset(fahrenheit), XtRImmediate, (XtPointer) FALSE},
576 {"dateFormat", XtCString, XtRString, sizeof(String),
577 offset(dateFormat), XtRString, (XtPointer) DEFAULT_DATE_FORMAT},
578 {"acpiDir", XtCString, XtRString, sizeof(String),
579 offset(acpiDir), XtRString, (XtPointer) DEFAULT_ACPI_DIR},
580 {"cpuFreqDir", XtCString, XtRString, sizeof(String),
581 offset(cpuFreqDir), XtRString, (XtPointer) DEFAULT_CPUFREQ_DIR},
584 static void quit(Widget w, XtPointer client_data, XtPointer call_data){
588 static XtActionsRec main_actions[] = { {"Quit", (XtActionProc) quit},
591 static String main_translations = "<Message>WM_PROTOCOLS:Quit()\n";
593 int main(int argc, char **argv){
596 int x = 0, y = 0, height = 0, width = 0;
597 int current_width = 0, own_width;
599 Atom wm_protocols[1];
604 topLevel = XtVaAppInitialize(&app_context, "XAcpi",
605 options, XtNumber(options),
606 &argc, argv, NULL, NULL);
608 XtGetApplicationResources(topLevel,
614 if(iv.criticalTemp <= 0) {
615 if(iv.fahrenheit) iv.criticalTemp = 158;
616 else iv.criticalTemp = 70;
619 check_acpi(iv.acpiDir);
621 if (iv.delay < 1) iv.delay = DEFAULT_DELAY;
623 XParseGeometry(iv.geometry, &x, &y, &width, &height);
625 while ((c = getopt(argc, argv, "DVH")) != -1)
631 fprintf(stderr, "xacpi version " XACPI_VERSION ".\n");
632 fprintf(stderr, "$Id: xacpi.c,v 1.40 2004/12/03 07:52:30 fleuret Exp $.\n");
639 " -highcolor <string: color>\n"
640 " -lowcolor <string: color>\n"
641 " -criticalcolor <string: color>\n"
642 " -chargingcolor <string: color>\n"
643 " -highvalue <int: percentage>\n"
644 " -lowvalue <int: percentage>\n"
645 " -delay <int: seconds>\n"
652 " -dateformat <string: format>\n"
654 " -acpidir <string: path>\n"
655 " -cpufreqdir <string: path>\n"
657 "See man xacpi for more detailed information.\n");
662 form = XtVaCreateManagedWidget("form",
663 formWidgetClass, topLevel,
664 XtNorientation, XtorientHorizontal,
666 XtNdefaultDistance, 0,
672 date = XtVaCreateManagedWidget("date",
673 labelWidgetClass, form,
674 XtNinternalHeight, 0,
681 char buf[BUFFER_LEN];
682 put_date(buf, iv.dateFormat);
684 XtVaGetValues(last, XtNfont, &fs, NULL);
685 own_width = ((strlen(buf) + 2) * fs->max_bounds.width);
686 XtVaSetValues(last, XtNwidth, own_width, NULL);
687 current_width += own_width;
690 /* Add the widget to display the remaining battery time */
692 delay = XtVaCreateManagedWidget("delay",
693 commandWidgetClass, form,
694 XtNleft, XtChainLeft,
695 XtNhighlightThickness, 0,
696 XtNinternalHeight, 0,
704 if(date) XtVaSetValues(delay, XtNfromHoriz, last, 0);
705 XtAddCallback(delay, XtNcallback, press, NULL);
708 XtVaGetValues(last, XtNfont, &fs, NULL);
709 own_width = 6 * fs->max_bounds.width;
710 XtVaSetValues(last, XtNwidth, own_width, NULL);
711 current_width += own_width;
713 /* Add the widget to display the colored bar */
717 scrollbar = XtVaCreateManagedWidget("scrollbar",
718 scrollbarWidgetClass, form,
721 XtNorientation, XtorientHorizontal,
724 XawScrollbarSetThumb(scrollbar, 0.0, 0.0);
726 XtVaSetValues(scrollbar,
727 XtNtranslations, XtParseTranslationTable(""),
731 } else scrollbar = 0;
733 /* Add the widget to display the temperature */
735 if(iv.showTemperature) {
736 temperature = XtVaCreateManagedWidget("temperature",
737 commandWidgetClass, form,
740 XtNhighlightThickness, 0,
741 XtNinternalHeight, 0,
744 XtNlabel, iv.fahrenheit ? "000F " : "00C ",
748 XtVaGetValues(last, XtNfont, &fs, NULL);
749 own_width = (iv. fahrenheit ? 5 : 4) * fs->max_bounds.width;
750 XtVaSetValues(last, XtNwidth, own_width, NULL);
751 current_width += own_width;
752 XtAddCallback(temperature, XtNcallback, press, NULL);
753 } else temperature = 0;
755 /* Add the widget to display the cpu frequency */
758 cpufreq = XtVaCreateManagedWidget("cpufreq",
759 labelWidgetClass, form,
762 XtNinternalHeight, 0,
769 XtVaGetValues(last, XtNfont, &fs, NULL);
770 own_width = 7 * fs->max_bounds.width;
771 XtVaSetValues(last, XtNwidth, own_width, NULL);
772 current_width += own_width;
775 /* Changed the bar length to the unused width */
778 if(width > current_width + MIN_BAR_WIDTH)
779 XtVaSetValues(scrollbar, XtNwidth, width - current_width, 0);
781 XtVaSetValues(scrollbar, XtNwidth, MIN_BAR_WIDTH, 0);
785 XtRealizeWidget(topLevel);
787 /* Add code to handle WM_DELETE_WINDOW cleanly. */
789 XtAppAddActions(app_context, main_actions, XtNumber(main_actions));
790 XtOverrideTranslations(topLevel, XtParseTranslationTable(main_translations));
792 = XInternAtom(XtDisplay(topLevel), "WM_DELETE_WINDOW", False);
793 XSetWMProtocols(XtDisplay(topLevel), XtWindow(topLevel), wm_protocols, 1);
795 timerId = XtAppAddTimeOut(app_context, 0, update, app_context);
796 XtAppMainLoop(app_context);