1 /* GKrellM
2 | Copyright (C) 1999-2019 Bill Wilson
3 |
4 | Author: Bill Wilson billw@gkrellm.net
5 | Latest versions might be found at: http://gkrellm.net
6 |
7 |
8 | GKrellM is free software: you can redistribute it and/or modify it
9 | under the terms of the GNU General Public License as published by
10 | the Free Software Foundation, either version 3 of the License, or
11 | (at your option) any later version.
12 |
13 | GKrellM is distributed in the hope that it will be useful, but WITHOUT
14 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 | or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
16 | License for more details.
17 |
18 | You should have received a copy of the GNU General Public License
19 | along with this program. If not, see http://www.gnu.org/licenses/
20 |
21 |
22 | Additional permission under GNU GPL version 3 section 7
23 |
24 | If you modify this program, or any covered work, by linking or
25 | combining it with the OpenSSL project's OpenSSL library (or a
26 | modified version of that library), containing parts covered by
27 | the terms of the OpenSSL or SSLeay licenses, you are granted
28 | additional permission to convey the resulting work.
29 | Corresponding Source for a non-source form of such a combination
30 | shall include the source code for the parts of OpenSSL used as well
31 | as that of the covered work.
32 */
33
34 #include "gkrellm.h"
35 #include "gkrellm-private.h"
36 #include "gkrellm-sysdeps.h"
37
38 #include <math.h>
39
40
41 #define BAT_CONFIG_KEYWORD "battery"
42
43 typedef enum
44 {
45 BATTERYDISPLAY_PERCENT,
46 BATTERYDISPLAY_TIME,
47 BATTERYDISPLAY_RATE,
48 BATTERYDISPLAY_EOM /* end of modes */
49 }
50 BatteryDisplayMode;
51
52
53 typedef struct
54 {
55 gint id;
56 GkrellmPanel *panel;
57 GkrellmKrell *krell;
58 GkrellmDecal *power_decal;
59 GkrellmDecal *time_decal;
60
61 GkrellmAlert *alert;
62
63 gboolean enabled;
64
65 BatteryDisplayMode display_mode;
66 gfloat charge_rate; /* % / min */
67
68 gboolean present,
69 on_line,
70 charging;
71 gint percent;
72 gint time_left; /* In minutes, -1 if minutes unavail */
73 }
74 Battery;
75
76 static GList *battery_list;
77 static GkrellmMonitor *mon_battery;
78 static GtkWidget *battery_vbox;
79
80 static Battery *composite_battery,
81 *launch_battery;
82
83 static gboolean enable_composite,
84 enable_each,
85 enable_estimate;
86
87 static gint poll_interval = 5,
88 full_cap_fallback = 5000;
89
90 static GkrellmLauncher launch;
91
92 static GkrellmAlert *bat_alert; /* One alert dupped for each battery */
93
94 static gint style_id;
95
96 static gboolean alert_units_percent,
97 alert_units_need_estimate_mode;
98
99 static void (*read_battery_data)();
100 static void create_battery_panel(Battery *bat, gboolean first_create);
101
102 static gint n_batteries;
103
104
105 static Battery *
battery_nth(gint n,gboolean create)106 battery_nth(gint n, gboolean create)
107 {
108 Battery *bat;
109
110 if (n > 10)
111 return NULL;
112 if (n < 0)
113 {
114 if (!composite_battery && create)
115 {
116 bat = g_new0(Battery, 1);
117 battery_list = g_list_prepend(battery_list, bat);
118 bat->id = GKRELLM_BATTERY_COMPOSITE_ID; /* -1 */
119 composite_battery = bat;
120 gkrellm_alert_dup(&bat->alert, bat_alert);
121 }
122 return composite_battery;
123 }
124
125 if (composite_battery)
126 ++n;
127
128 while ( (bat = (Battery *) g_list_nth_data(battery_list, n)) == NULL
129 && create
130 )
131 {
132 bat = g_new0(Battery, 1);
133 battery_list = g_list_append(battery_list, bat);
134 bat->id = n_batteries++;
135 gkrellm_alert_dup(&bat->alert, bat_alert);
136 }
137 return bat;
138 }
139
140
141 /* Themers need to be able to see the battery monitor.
142 */
143 static void
read_battery_demo(void)144 read_battery_demo(void)
145 {
146 gboolean on_line, charging;
147 gint percent, time_left;
148 static gint bump = 60;
149
150 if (bump <= 5)
151 bump = 60;
152 bump -= 5;
153 on_line = bump > 45;
154 if (on_line)
155 {
156 charging = TRUE;
157 time_left = 200 + (60 - bump) * 20;
158 percent = time_left / 5;
159 }
160 else
161 {
162 charging = FALSE;
163 time_left = bump;
164 percent = 1 + bump;
165 }
166 gkrellm_battery_assign_data(0, TRUE, on_line, charging,
167 percent, time_left);
168 }
169
170
171 static gboolean
setup_battery_interface(void)172 setup_battery_interface(void)
173 {
174 if (!read_battery_data && !_GK.client_mode && gkrellm_sys_battery_init())
175 read_battery_data = gkrellm_sys_battery_read_data;
176 if (_GK.demo)
177 read_battery_data = read_battery_demo;
178 return read_battery_data ? TRUE : FALSE;
179 }
180
181 void
gkrellm_battery_client_divert(void (* read_func)())182 gkrellm_battery_client_divert(void (*read_func)())
183 {
184 read_battery_data = read_func;
185 }
186
187 void
gkrellm_battery_assign_data(gint n,gboolean present,gboolean on_line,gboolean charging,gint percent,gint time_left)188 gkrellm_battery_assign_data(gint n, gboolean present, gboolean on_line,
189 gboolean charging, gint percent, gint time_left)
190 {
191 Battery *bat;
192
193 bat = battery_nth(n, TRUE);
194 if (!bat)
195 return;
196 bat->present = present;
197 bat->on_line = on_line;
198 bat->charging = charging;
199 bat->percent = percent;
200 bat->time_left = time_left;
201 }
202
203 /* Help out some laptops with Linux ACPI bugs */
204 gint
gkrellm_battery_full_cap_fallback(void)205 gkrellm_battery_full_cap_fallback(void)
206 {
207 return full_cap_fallback;
208 }
209
210
211 /* -------------------------------------------------------------- */
212
213
214 /* estimate (guess-timate?) battery time remaining, based on the rate of
215 discharge (and conversely the time to charge based on the rate of charge).
216 - some BIOS' only provide battery levels, not any estimate of the time
217 remaining
218
219 Battery charge/discharge characteristics (or, why dc/dt doesn't really work)
220 - the charge/discharge curves of most battery types tend to be very non-
221 linear (http://www.google.com/search?q=battery+charge+discharge+curve)
222 - on discharge, most battery types will initially fall somewhat rapidly
223 from 100 percent, then flatten out and stay somewhat linear until
224 suddenly "dropping out" when nearly depleted (approx. 10-20% capacity).
225 For practical purposes we can consider this point to be the end of the
226 discharge curve. This is simple enough to model via a fixed capacity
227 offset to cut out just at the knee of this curve, and allows us to
228 reasonably approximate the rest of the curve by a linear function
229 and simple dc/dt calculation.
230 - with regard to charging, however, it's not quite so easy. With a
231 constant voltage charger, the battery capacity rises exponentially
232 (charging current decreases as battery terminal voltage rises). The
233 final stages of charging are very gradual, with a relatively long
234 period at "almost but not quite 100%".
235
236 Unfortunately a linear extrapolation at the beginning of an
237 exponential curve will be a poor approximation to the true expected
238 time to charge, tending to be significantly undervalued. Using an
239 exponential model to estimate time to approx. 90-95% (2.5 * exp. time
240 constant) seems to give a more reasonable fit. That said, the poor
241 relative resolution at higher charge values makes estimating the
242 exponential time constant difficult towards the end of the charge
243 cycle (the curve's very nearly flat). So, I've settled on a mixed
244 model - for c < ~70 I use an exponential model, and switch to linear
245 above that (or if the charge rate seems to have otherwise "flatlined").
246
247 Empirically, this method seems to give reasonable results [1] -
248 certainly much better than seeing "0:50:00 to full" for a good half an
249 hour (i.e. as happens with apmd, which uses a linear model for both
250 charging + discharging). Note that a constant-current charger should
251 be pretty well linear all the way along the charge curve, which means
252 the linear rate extrapolation should work well in this case. The user
253 can choose which model they wish to use via estimate_model.
254
255 [1] I logged my Compaq Armada M300's capacity (via /proc/apm) over one
256 complete discharge/charge cycle (machine was idle the whole time). The
257 discharge curve was linear to approx. 14% when the BIOS alerted of
258 impending doom; upon plugging in the external power supply the capacity
259 rose exponentially to 100%, with a time constant of approx. 0.8 hr (i.e.
260 approx. 2+ hrs to full charge).
261
262 Linear rate of change calculation:
263 - in an ideal, continuous world, estimated time to 0(100) would simply
264 be the remaining capacity divided by the charge rate
265 ttl = c / dc(t)/dt
266 - alas, the reported battery capacity is bound to integer values thus
267 c(t) is a discontinuous function. i.e. has fairly large steps. And of
268 course then dc/dt is undefined at the discontinuities.
269 - to avoid this issue the rate of charge is determined by the deltas from
270 the start of the last state change (charge/discharge cycle) (time T)
271 ttl(t) = c(t) / ((C - c(t)) / (T - t)) C = charge at time T
272 Furthermore, the rate changes are windowed and filtered to mitigate
273 c(t) transients (e.g. at the start of discharge) and smooth over
274 discontinuities (and fudge for battery characteristics, ref. above).
275 */
276
277 #define BAT_SLEEP_DETECT 300 /* interval of >300s => must have slept */
278 #define BAT_DISCHARGE_TRANSIENT 10 /* ignore first 10% of discharge cycle */
279 #define BAT_EMPTY_CAPACITY 12 /* when is the battery "flat"? */
280 #define BAT_RATECHANGE_WINDOW 90 /* allow rate changes after 90s */
281 #define BAT_CHARGE_MODEL_LIMIT 60 /* linear charge model cutover point */
282 #define BAT_RATE_SMOOTHING 0.3 /* rate smoothing weight */
283
284 /* #define BAT_ESTIMATE_DEBUG */
285
286 /* user-provided nominal battery runtimes, hrs (used to determine initial
287 | discharge, stable, charge rate (%/min))
288 */
289 static gfloat estimate_runtime[2] = {0};
290 static gint estimate_model[2] = {0};
291 static gboolean reset_estimate;
292
293 static void
estimate_battery_time_left(Battery * bat)294 estimate_battery_time_left(Battery *bat)
295 {
296 /* ?0 = at time 0 (state change); ?1 = at last "sample" (rate change) */
297 static time_t t0 = -1, t1;
298 static gint p0, p1;
299 static gint c0;
300 static gfloat rate = 0;
301 static time_t dt;
302 static gint dp;
303 time_t t = time(NULL);
304
305 /* 1 charging; 0 power on and stable; -1 discharging */
306 gint charging = bat->charging ? 1 : (bat->on_line ? 0 : -1);
307
308 #ifdef BAT_ESTIMATE_DEBUG
309 fprintf(stderr, "%ld bc?=%d ac?=%d (%+d) bp=%d\t", t,
310 bat->charging, bat->on_line, charging,
311 bat->percent);
312 #endif
313
314 if ( reset_estimate || t0 < 0 || c0 != charging
315 || (t - t1) > BAT_SLEEP_DETECT
316 )
317 {
318 /* init, state change, or sleep/hibernation
319 */
320 reset_estimate = FALSE;
321 c0 = charging;
322
323 t0 = t1 = t;
324 if (charging < 0 && (bat->percent > 100 - BAT_DISCHARGE_TRANSIENT))
325 p0 = p1 = 100 - BAT_DISCHARGE_TRANSIENT;
326 else
327 p0 = p1 = bat->percent;
328 dp = dt = 0;
329 rate = 0.0;
330
331 /* convert runtime (hrs) to signed rate (%/min)
332 */
333 if (charging < 0)
334 rate = -100 / (estimate_runtime[0] * 60);
335 else if (charging > 0)
336 rate = 100 / (estimate_runtime[1] * 60);
337
338 #ifdef BAT_ESTIMATE_DEBUG
339 fprintf(stderr, "[r = %.3f]\t", rate);
340 #endif
341 }
342 else
343 {
344 time_t dt1 = t - t1; /* delta since last rate change */
345 gint dp1 = bat->percent - p1;
346
347 /* time for a rate change?
348 */
349 if ( dt1 > BAT_RATECHANGE_WINDOW
350 && ((charging > 0 && dp1 >= 0) || (charging < 0 && dp1 <= 0))
351 )
352 {
353 dt = t - t0; /* since state change */
354 dp = bat->percent - p0;
355
356 if (dp1 == 0) /* flatlining (dp/dt = 0) */
357 rate = (1 - BAT_RATE_SMOOTHING/4) * rate;
358 else
359 rate = BAT_RATE_SMOOTHING *
360 ((gdouble) dp / (gdouble) (dt/60)) +
361 (1 - BAT_RATE_SMOOTHING) * rate;
362
363 #ifdef BAT_ESTIMATE_DEBUG
364 fprintf(stderr, "%d [dp = %+d dt = %.2f rate = %.3f]\t",
365 (gint) dp1, dp, (gdouble) dt / 60, rate);
366 #endif
367
368 t1 = t;
369 p1 = bat->percent;
370 }
371 }
372
373 if (charging && rate != 0.0) /* (dis)charging */
374 {
375 gfloat eta;
376 gint p = charging > 0 ? 100 - bat->percent :
377 bat->percent - BAT_EMPTY_CAPACITY;
378
379 if ( charging > 0 && estimate_model[1]
380 && bat->percent < BAT_CHARGE_MODEL_LIMIT && dp > 0
381 )
382 /* charging, use exponential: eta =~ 2.5 * time-constant (~=92%) */
383 eta = -2.5 * dt/60 / (log(1 - (gdouble)dp/(gdouble)(p+dp)));
384 else
385 eta = fabs((gdouble)p / rate); /* use linear */
386
387 #ifdef BAT_ESTIMATE_DEBUG
388 fprintf(stderr, "eta = %.2f\t", eta);
389 #endif
390
391 /* round off to nearest 5 mins */
392 bat->time_left = (gint)((eta > 0 ? eta + 2.5: 0) / 5) * 5;
393 bat->charge_rate = rate;
394 }
395 else
396 {
397 bat->time_left = INT_MAX; /* inf */
398 bat->charge_rate = 0.0;
399 }
400
401 #ifdef BAT_ESTIMATE_DEBUG
402 fprintf(stderr, "\n");
403 #endif
404 }
405
406 static void
draw_time_left_decal(Battery * bat,gboolean force)407 draw_time_left_decal(Battery *bat, gboolean force)
408 {
409 GkrellmDecal *d;
410 gchar buf[16];
411 gint x, w, t;
412 int battery_display_mode = bat->display_mode;
413 static BatteryDisplayMode last_mode = BATTERYDISPLAY_EOM;
414
415 if (!bat->panel)
416 return;
417 if (bat->time_left == -1)
418 battery_display_mode = BATTERYDISPLAY_PERCENT;
419 if (last_mode != battery_display_mode)
420 force = TRUE;
421 last_mode = bat->display_mode;
422
423 switch (battery_display_mode)
424 {
425 case BATTERYDISPLAY_TIME:
426 t = bat->time_left;
427 if (t == INT_MAX || t == INT_MIN)
428 snprintf(buf, sizeof(buf), "--");
429 else
430 snprintf(buf, sizeof(buf), "%2d:%02d", t / 60, t % 60);
431 break;
432
433 case BATTERYDISPLAY_RATE:
434 /* t is used by draw_decal_text() to see if a refresh is reqd */
435 t = (gint) (bat->charge_rate * 100.0);
436 snprintf(buf, sizeof(buf), "%0.1f%%/m",
437 bat->charge_rate);
438 break;
439
440 case BATTERYDISPLAY_PERCENT:
441 default:
442 t = bat->percent;
443 if (t == -1) /* APM battery flags should cause hide... but */
444 snprintf(buf, sizeof(buf), "no bat");
445 else
446 snprintf(buf, sizeof(buf), "%d%%", t);
447 break;
448 }
449
450 d = bat->time_decal;
451 w = gkrellm_gdk_string_width(d->text_style.font, buf);
452 x = (d->w - w) / 2;
453 if (x < 0)
454 x = 0;
455 d->x_off = x;
456 gkrellm_draw_decal_text(bat->panel, d, buf, force ? -1 : t);
457 }
458
459 static void
update_battery_panel(Battery * bat)460 update_battery_panel(Battery *bat)
461 {
462 GkrellmPanel *p = bat->panel;
463
464 if (!p)
465 return;
466 if (!bat->present)
467 { /* Battery can be removed while running */
468 gkrellm_panel_hide(p);
469 return;
470 }
471 gkrellm_panel_show(p);
472
473 if (bat->time_left > 0 && bat->charging)
474 bat->charge_rate = (gfloat) (100 - bat->percent) / (gfloat) bat->time_left;
475 else
476 bat->charge_rate = 0.0;
477
478 if (enable_estimate)
479 estimate_battery_time_left(bat);
480
481 if (bat->on_line)
482 {
483 gkrellm_reset_alert(bat->alert);
484 gkrellm_freeze_alert(bat->alert);
485 gkrellm_draw_decal_pixmap(p, bat->power_decal, D_MISC_AC);
486 }
487 else
488 {
489 if ( (bat == composite_battery && enable_composite)
490 || (bat != composite_battery && enable_each)
491 )
492 {
493 gkrellm_thaw_alert(bat->alert);
494 gkrellm_check_alert(bat->alert, alert_units_percent
495 ? bat->percent : bat->time_left);
496 }
497 gkrellm_draw_decal_pixmap(p, bat->power_decal, D_MISC_BATTERY);
498 }
499 draw_time_left_decal(bat, FALSE);
500 gkrellm_update_krell(p, bat->krell, bat->percent);
501 gkrellm_draw_panel_layers(p);
502 }
503
504 static void
update_battery(void)505 update_battery(void)
506 {
507 GList *list;
508 Battery *bat;
509 static gint seconds = 0;
510
511 if (!enable_each && !enable_composite)
512 return;
513 if (GK.second_tick)
514 {
515 if (seconds == 0)
516 {
517 for (list = battery_list; list; list = list->next)
518 ((Battery *) list->data)->present = FALSE;
519
520 (*read_battery_data)();
521 for (list = battery_list; list; list = list->next)
522 {
523 bat = (Battery *) list->data;
524 if (!bat->panel)
525 create_battery_panel(bat, TRUE);
526 if (bat->enabled)
527 update_battery_panel(bat);
528 }
529 }
530 seconds = (seconds + 1) % poll_interval;
531 }
532 }
533
534 static gboolean
cb_expose_event(GtkWidget * widget,GdkEventExpose * ev,GkrellmPanel * p)535 cb_expose_event(GtkWidget *widget, GdkEventExpose *ev, GkrellmPanel *p)
536 {
537 gdk_draw_drawable(gtk_widget_get_window(widget),
538 gtk_widget_get_style(widget)->fg_gc[gtk_widget_get_state(widget)], p->pixmap,
539 ev->area.x, ev->area.y, ev->area.x, ev->area.y,
540 ev->area.width, ev->area.height);
541 return FALSE;
542 }
543
544 static gboolean
cb_panel_enter(GtkWidget * w,GdkEventButton * ev,Battery * bat)545 cb_panel_enter(GtkWidget *w, GdkEventButton *ev, Battery *bat)
546 {
547 gkrellm_decal_on_top_layer(bat->time_decal, TRUE);
548 gkrellm_draw_panel_layers(bat->panel);
549 return FALSE;
550 }
551
552 static gboolean
cb_panel_leave(GtkWidget * w,GdkEventButton * ev,Battery * bat)553 cb_panel_leave(GtkWidget *w, GdkEventButton *ev, Battery *bat)
554 {
555 gkrellm_decal_on_top_layer(bat->time_decal, FALSE);
556 gkrellm_draw_panel_layers(bat->panel);
557 return FALSE;
558 }
559
560
561 static gboolean
cb_panel_press(GtkWidget * widget,GdkEventButton * ev,Battery * bat)562 cb_panel_press(GtkWidget *widget, GdkEventButton *ev, Battery *bat)
563 {
564 GkrellmDecal *d;
565 static gboolean time_unavailable_warned;
566
567 d = launch.decal;
568 if (ev->button == 3)
569 gkrellm_open_config_window(mon_battery);
570 else if ( ev->button == 2
571 || (ev->button == 1 && !d)
572 || (ev->button == 1 && d && ev->x < d->x)
573 )
574 {
575 if (bat->time_left == -1 && bat->present)
576 {
577 if (!time_unavailable_warned)
578 gkrellm_message_dialog(_("GKrellM Battery"),
579 _("Battery times are unavailable. You\n"
580 "could select the Estimated Time option."));
581 time_unavailable_warned = TRUE;
582 bat->display_mode = BATTERYDISPLAY_PERCENT;
583 }
584 else
585 {
586 bat->display_mode++;
587 if (bat->display_mode == BATTERYDISPLAY_EOM)
588 bat->display_mode = 0;
589
590 draw_time_left_decal(bat, TRUE);
591 gkrellm_draw_panel_layers(bat->panel);
592 gkrellm_config_modified();
593 }
594 }
595 return FALSE;
596 }
597
598 static void
create_battery_panel(Battery * bat,gboolean first_create)599 create_battery_panel(Battery *bat, gboolean first_create)
600 {
601 GkrellmPanel *p;
602 GkrellmStyle *style;
603 GkrellmMargin *m;
604 gint x, w;
605
606 if (!bat->panel)
607 bat->panel = gkrellm_panel_new0();
608 p = bat->panel;
609 style = gkrellm_meter_style(style_id);
610 m = gkrellm_get_style_margins(style);
611 bat->power_decal = gkrellm_create_decal_pixmap(p,
612 gkrellm_decal_misc_pixmap(), gkrellm_decal_misc_mask(),
613 N_MISC_DECALS, style, m->left, -1);
614
615 x = bat->power_decal->x + bat->power_decal->w + 2;
616 w = gkrellm_chart_width() - x - m->right;
617 bat->time_decal = gkrellm_create_decal_text(p, "8/%",
618 gkrellm_meter_textstyle(style_id),
619 style, x, -1, w);
620
621 bat->krell = gkrellm_create_krell(p,
622 gkrellm_krell_meter_piximage(style_id), style);
623 gkrellm_monotonic_krell_values(bat->krell, FALSE);
624 gkrellm_set_krell_full_scale(bat->krell, 100, 1);
625
626 gkrellm_panel_configure(p, NULL, style);
627 gkrellm_panel_create(battery_vbox, mon_battery, p);
628
629 /* Center the decals with respect to each other.
630 */
631 if (bat->power_decal->h > bat->time_decal->h)
632 bat->time_decal->y += (bat->power_decal->h - bat->time_decal->h) / 2;
633 else
634 bat->power_decal->y += (bat->time_decal->h - bat->power_decal->h) / 2;
635
636 if (first_create)
637 {
638 g_signal_connect(G_OBJECT(p->drawing_area), "expose_event",
639 G_CALLBACK(cb_expose_event), p);
640 g_signal_connect(G_OBJECT(p->drawing_area), "button_press_event",
641 G_CALLBACK(cb_panel_press), bat);
642 g_signal_connect(G_OBJECT(p->drawing_area), "enter_notify_event",
643 G_CALLBACK(cb_panel_enter), bat);
644 g_signal_connect(G_OBJECT(p->drawing_area), "leave_notify_event",
645 G_CALLBACK(cb_panel_leave), bat);
646 }
647
648 gkrellm_setup_decal_launcher(p, &launch, bat->time_decal);
649 if ( (bat == composite_battery && enable_composite)
650 || (bat->id == 0 && composite_battery && !enable_composite)
651 || (bat->id == 0 && !composite_battery)
652 )
653 launch_battery = bat;
654
655 if (bat == composite_battery)
656 bat->enabled = enable_composite;
657 else
658 bat->enabled = enable_each;
659
660 if (bat->enabled)
661 update_battery_panel(bat);
662 else
663 gkrellm_panel_hide(p);
664 }
665
666 static void
spacer_visibility(void)667 spacer_visibility(void)
668 {
669 GList *list;
670 Battery *bat;
671 gboolean enabled = FALSE;
672
673 for (list = battery_list; list; list = list->next)
674 {
675 bat = (Battery *) list->data;
676 enabled |= bat->enabled;
677 }
678 if (enabled)
679 gkrellm_spacers_show(mon_battery);
680 else
681 gkrellm_spacers_hide(mon_battery);
682 }
683
684 static void
create_battery(GtkWidget * vbox,gint first_create)685 create_battery(GtkWidget *vbox, gint first_create)
686 {
687 GList *list;
688 Battery *bat;
689
690 battery_vbox = vbox;
691 if (_GK.demo)
692 enable_each = TRUE;
693 for (list = battery_list; list; list = list->next)
694 {
695 bat = (Battery *) list->data;
696 create_battery_panel(bat, first_create);
697 }
698 spacer_visibility();
699 }
700
701
702 /* Expand alert command substitution variables:
703 | $H - hostname $n - battery id
704 | $t - time left $p - percent
705 | $o - online (boolean) $c - charging (boolean)
706 */
707 static void
cb_command_process(GkrellmAlert * alert,gchar * src,gchar * buf,gint size,Battery * bat)708 cb_command_process(GkrellmAlert *alert, gchar *src, gchar *buf, gint size,
709 Battery *bat)
710 {
711 gchar c, *s;
712 gint len, value;
713
714 if (!buf || size < 1)
715 return;
716 --size;
717 *buf = '\0';
718 if (!src)
719 return;
720 for (s = src; *s != '\0' && size > 0; ++s)
721 {
722 len = 1;
723 if (*s == '$' && *(s + 1) != '\0')
724 {
725 value = -1;
726 if ((c = *(s + 1)) == 'H')
727 len = snprintf(buf, size, "%s", gkrellm_sys_get_host_name());
728 else if (c == 'n' && bat != composite_battery)
729 value = bat->id;
730 else if (c == 't')
731 value = bat->time_left;
732 else if (c == 'p')
733 value = bat->percent;
734 else if (c == 'o')
735 value = bat->on_line;
736 else if (c == 'c')
737 value = bat->charging;
738 else
739 len = 0;
740
741 if (value >= 0)
742 len = snprintf(buf, size, "%d", value);
743 ++s;
744 }
745 else
746 *buf = *s;
747 size -= len;
748 buf += len;
749 }
750 *buf = '\0';
751 }
752
753 static void
cb_battery_alert_trigger(GkrellmAlert * alert,Battery * bat)754 cb_battery_alert_trigger(GkrellmAlert *alert, Battery *bat)
755 {
756 GkrellmAlertdecal *ad;
757 GkrellmDecal *d;
758
759 alert->panel = bat->panel;
760 ad = &alert->ad;
761 d = bat->time_decal;
762 ad->x = d->x + 1;
763 ad->y = d->y - 2;
764 ad->w = d->w - 1;
765 ad->h = d->h + 4;
766 gkrellm_render_default_alert_decal(alert);
767 }
768
769 static void
dup_battery_alert(void)770 dup_battery_alert(void)
771 {
772 GList *list;
773 Battery *bat;
774
775 for (list = battery_list; list; list = list->next)
776 {
777 bat = (Battery *) list->data;
778 gkrellm_alert_dup(&bat->alert, bat_alert);
779 gkrellm_alert_trigger_connect(bat->alert,
780 cb_battery_alert_trigger, bat);
781 gkrellm_alert_command_process_connect(bat->alert,
782 cb_command_process, bat);
783 }
784 }
785
786
787 /* If the OS reports battery times, alerts will always have minutes units.
788 | If the OS does not report battery times the initial alert create will
789 | have minutes units if the estimate time option is enabled and it will
790 | have battery percent level units if estimate time option is off. Alert
791 | creates from load config will have units in effect at last save config.
792 */
793 static void
create_alert(void)794 create_alert(void)
795 {
796 Battery *bat;
797
798 if (!battery_list)
799 return;
800 bat = (Battery *) battery_list->data;
801
802 if (!bat_alert)
803 {
804 alert_units_need_estimate_mode = FALSE;
805
806 if ( alert_units_percent
807 || (bat->time_left == -1 && !enable_estimate)
808 )
809 {
810 if (bat->time_left == -1)
811 alert_units_percent = TRUE;
812 bat_alert = gkrellm_alert_create(NULL, _("Battery"),
813 _("Battery Percent Limits"),
814 FALSE, TRUE, TRUE, 99, 0, 1, 10, 0);
815 }
816 else
817 {
818 bat_alert = gkrellm_alert_create(NULL, _("Battery"),
819 _("Battery Minutes Remaining Limits"),
820 FALSE, TRUE, TRUE, 500, 0, 1, 10, 0);
821 if (bat->time_left == -1)
822 alert_units_need_estimate_mode = TRUE;
823 }
824 }
825 gkrellm_alert_config_connect(bat_alert, dup_battery_alert, NULL);
826
827 /* This alert is a master to be dupped and is itself never checked */
828 }
829
830
831 static void
save_battery_config(FILE * f)832 save_battery_config(FILE *f)
833 {
834 GList *list;
835 Battery *bat;
836
837 fprintf(f, "%s enable %d\n", BAT_CONFIG_KEYWORD, enable_each);
838 fprintf(f, "%s enable_composite %d\n", BAT_CONFIG_KEYWORD,
839 enable_composite);
840 fprintf(f, "%s estimate_time %d\n", BAT_CONFIG_KEYWORD, enable_estimate);
841
842 /* 2.1.15: scale saved float values to avoid decimal points in the config.
843 */
844 fprintf(f, "%s estimate_time_discharge %.0f\n", BAT_CONFIG_KEYWORD,
845 estimate_runtime[0] * GKRELLM_FLOAT_FACTOR);
846 fprintf(f, "%s estimate_time_charge %.0f\n", BAT_CONFIG_KEYWORD,
847 estimate_runtime[1] * GKRELLM_FLOAT_FACTOR);
848 fprintf(f, "%s estimate_time_charge_model %d\n", BAT_CONFIG_KEYWORD,
849 estimate_model[1]);
850 fprintf(f, "%s full_cap_fallback %d\n", BAT_CONFIG_KEYWORD,
851 full_cap_fallback);
852 if (!_GK.client_mode)
853 fprintf(f, "%s poll_interval %d\n", BAT_CONFIG_KEYWORD, poll_interval);
854 if (launch.command)
855 fprintf(f, "%s launch1 %s\n", BAT_CONFIG_KEYWORD, launch.command);
856 if (launch.tooltip_comment)
857 fprintf(f, "%s tooltip_comment %s\n",
858 BAT_CONFIG_KEYWORD, launch.tooltip_comment);
859 fprintf(f, "%s alert_units_percent %d\n", BAT_CONFIG_KEYWORD,
860 alert_units_percent);
861 for (list = battery_list; list; list = list->next)
862 {
863 bat = (Battery *) list->data;
864 if (bat == composite_battery) /* Don't 2.1.16 backwards break */
865 fprintf(f, "%s display_mode_composite %d %d\n", BAT_CONFIG_KEYWORD,
866 bat->display_mode, bat->id);
867 else
868 fprintf(f, "%s display_mode %d %d\n", BAT_CONFIG_KEYWORD,
869 bat->display_mode, bat->id);
870 }
871 gkrellm_save_alertconfig(f, bat_alert, BAT_CONFIG_KEYWORD, NULL);
872 }
873
874 static void
load_battery_config(gchar * arg)875 load_battery_config(gchar *arg)
876 {
877 Battery *bat;
878 gint display_mode, n = 0;
879 gchar config[32], item[CFG_BUFSIZE],
880 name[CFG_BUFSIZE], item1[CFG_BUFSIZE];
881
882 if (sscanf(arg, "%31s %[^\n]", config, item) == 2)
883 {
884 if (!strcmp(config, "enable"))
885 sscanf(item, "%d", &enable_each);
886 if (!strcmp(config, "enable_composite"))
887 sscanf(item, "%d", &enable_composite);
888 else if (!strcmp(config, "estimate_time"))
889 sscanf(item, "%d", &enable_estimate);
890 else if (!strcmp(config, "estimate_time_discharge"))
891 {
892 sscanf(item, "%f", &estimate_runtime[0]);
893 estimate_runtime[0] /= _GK.float_factor;
894 }
895 else if (!strcmp(config, "estimate_time_charge"))
896 {
897 sscanf(item, "%f", &estimate_runtime[1]);
898 estimate_runtime[1] /= _GK.float_factor;
899 }
900 else if (!strcmp(config, "estimate_time_charge_model"))
901 sscanf(item, "%d", &estimate_model[1]);
902 else if (!strcmp(config, "full_cap_fallback"))
903 sscanf(item, "%d", &full_cap_fallback);
904 else if (!strcmp(config, "poll_interval"))
905 sscanf(item, "%d", &poll_interval);
906 else if (!strcmp(config, "launch1"))
907 launch.command = g_strdup(item);
908 else if (!strcmp(config, "tooltip_comment"))
909 launch.tooltip_comment = g_strdup(item);
910 else if (!strncmp(config, "display_mode", 12))
911 {
912 sscanf(item, "%d %d", &display_mode, &n);
913 if ((bat = battery_nth(n, FALSE)) != NULL)
914 bat->display_mode = display_mode;
915 }
916 else if (!strcmp(config, "alert_units_percent"))
917 sscanf(item, "%d", &alert_units_percent);
918 else if (!strcmp(config, GKRELLM_ALERTCONFIG_KEYWORD))
919 {
920 if (!strncmp(item, "BAT", 3)) /* Config compat musical chairs */
921 sscanf(item, "%32s %[^\n]", name, item1);
922 else
923 strcpy(item1, item);
924 create_alert();
925 gkrellm_load_alertconfig(&bat_alert, item1);
926 dup_battery_alert();
927 }
928 }
929 }
930
931
932 static GtkWidget *launch_entry,
933 *tooltip_entry;
934
935 static GtkWidget *estimate_runtime_spin_button[2],
936 *estimate_model_button[2];
937
938 static void
update_battery_panels(void)939 update_battery_panels(void)
940 {
941 GList *list;
942 Battery *bat;
943
944 for (list = battery_list; list; list = list->next)
945 {
946 bat = (Battery *) list->data;
947 if (bat->enabled)
948 update_battery_panel(bat);
949 }
950 }
951
952 static void
cb_set_alert(GtkWidget * button,Battery * bat)953 cb_set_alert(GtkWidget *button, Battery *bat)
954 {
955 create_alert();
956 gkrellm_alert_config_window(&bat_alert);
957 }
958
959 static void
alert_units_percent_cb(GtkToggleButton * button,gpointer data)960 alert_units_percent_cb(GtkToggleButton *button, gpointer data)
961 {
962 GList *list;
963 Battery *bat;
964
965 alert_units_percent = button->active;
966
967 if (bat_alert)
968 {
969 for (list = battery_list; list; list = list->next)
970 {
971 bat = (Battery *) list->data;
972 gkrellm_reset_alert(bat->alert);
973 gkrellm_alert_destroy(&bat->alert);
974 }
975 gkrellm_alert_destroy(&bat_alert);
976 gkrellm_config_message_dialog(_("GKrellM Battery"),
977 _("The Battery alert units are changed\n"
978 "and the alert must be reconfigured."));
979 }
980 }
981
982 static void
cb_enable_estimate(GtkToggleButton * button,GtkWidget * box)983 cb_enable_estimate(GtkToggleButton *button, GtkWidget *box)
984 {
985 GList *list;
986 Battery *bat;
987 gboolean enable;
988
989 enable = button->active;
990 gtk_widget_set_sensitive(box, enable);
991
992 if (enable_estimate != enable)
993 {
994 /* If alert units need estimated time mode and estimation switches off,
995 | destroy the alert because the alert units can now only be percent.
996 */
997 for (list = battery_list; list; list = list->next)
998 {
999 bat = (Battery *) list->data;
1000 if (bat->alert && (!enable && alert_units_need_estimate_mode))
1001 gkrellm_alert_destroy(&bat->alert);
1002 }
1003 if ( bat_alert
1004 && (!enable && alert_units_need_estimate_mode)
1005 && !alert_units_percent
1006 )
1007 {
1008 gkrellm_alert_destroy(&bat_alert);
1009 gkrellm_config_message_dialog(_("GKrellM Battery"),
1010 _("The Battery alert units are changed\n"
1011 "and the alert must be reconfigured."));
1012 }
1013 }
1014 enable_estimate = enable;
1015 update_battery_panels();
1016 }
1017
1018 static void
cb_runtime(GtkWidget * entry,gpointer data)1019 cb_runtime(GtkWidget *entry, gpointer data)
1020 {
1021 gint i = GPOINTER_TO_INT(data) - 1;
1022
1023 estimate_runtime[i] = gtk_spin_button_get_value(
1024 GTK_SPIN_BUTTON(estimate_runtime_spin_button[i]));
1025 reset_estimate = TRUE;
1026 update_battery_panels();
1027 }
1028
1029
1030 static void
cb_enable(GtkToggleButton * button,gpointer data)1031 cb_enable(GtkToggleButton *button, gpointer data)
1032 {
1033 GList *list;
1034 Battery *bat;
1035 gint which = GPOINTER_TO_INT(data);
1036
1037 if (which == 0)
1038 enable_composite = enable_each = button->active;
1039 else if (which == 1)
1040 enable_each = button->active;
1041 else if (which == 2)
1042 enable_composite = button->active;
1043
1044 for (list = battery_list; list; list = list->next)
1045 {
1046 bat = (Battery *) list->data;
1047 if (bat == composite_battery)
1048 bat->enabled = enable_composite;
1049 else
1050 bat->enabled = enable_each;
1051
1052 if (bat->enabled)
1053 {
1054 gkrellm_panel_show(bat->panel);
1055 update_battery_panel(bat);
1056 }
1057 else
1058 {
1059 gkrellm_reset_alert(bat->alert);
1060 gkrellm_panel_hide(bat->panel);
1061 }
1062 }
1063 if (composite_battery)
1064 {
1065 gkrellm_remove_launcher(&launch);
1066
1067 if (composite_battery->enabled)
1068 {
1069 gkrellm_setup_decal_launcher(composite_battery->panel,
1070 &launch, composite_battery->time_decal);
1071 launch_battery = composite_battery;
1072 }
1073 else
1074 {
1075 bat = battery_nth(0, FALSE);
1076 if (bat && bat->enabled)
1077 {
1078 gkrellm_setup_decal_launcher(bat->panel,
1079 &launch, bat->time_decal);
1080 launch_battery = bat;
1081 }
1082 }
1083
1084 }
1085 spacer_visibility();
1086 }
1087
1088 static void
cb_estimate_model(GtkWidget * entry,gpointer data)1089 cb_estimate_model(GtkWidget *entry, gpointer data)
1090 {
1091 gint i = GPOINTER_TO_INT(data);
1092
1093 estimate_model[i] =
1094 GTK_TOGGLE_BUTTON(estimate_model_button[i])->active;
1095 reset_estimate = TRUE;
1096 update_battery_panels();
1097 }
1098
1099 static void
cb_poll_interval(GtkWidget * entry,GtkSpinButton * spin)1100 cb_poll_interval(GtkWidget *entry, GtkSpinButton *spin)
1101 {
1102 poll_interval = gtk_spin_button_get_value_as_int(spin);
1103 }
1104
1105 static void
cb_launch_entry(GtkWidget * widget,gpointer data)1106 cb_launch_entry(GtkWidget *widget, gpointer data)
1107 {
1108 if (!launch_battery)
1109 return;
1110 gkrellm_apply_launcher(&launch_entry, &tooltip_entry,
1111 launch_battery->panel, &launch, gkrellm_launch_button_cb);
1112 }
1113
1114
1115 static gchar *battery_info_text[] =
1116 {
1117 N_("<h>Setup\n"),
1118 N_("<b>Display Estimated Time\n"),
1119 N_("If battery times are not reported by the BIOS or if the reported times\n"
1120 "are inaccurate, select this option to display a battery time remaining or\n"
1121 "time to charge which is calculated based on the current battery percentage\n"
1122 "level, user supplied total battery times, and a linear extrapolation model.\n"),
1123 "\n",
1124 N_("<b>Total Battery Times\n"),
1125 N_("Enter the typical total run time and total charge time in hours for your\n"
1126 "battery.\n"),
1127 "\n",
1128 N_("<b>Exponential Charge Model\n"), /* xgettext:no-c-format */
1129 N_("For some charging systems battery capacity rises exponentially, which\n"
1130 "means the simple linear model will grossly underestimate the time to 100%.\n"
1131 "Select the exponential model for more accuracy in this case.\n"),
1132 "\n",
1133 "<b>",
1134 N_("Alerts"),
1135 "\n",
1136 N_("Substitution variables may be used in alert commands.\n"),
1137 N_("\t$p battery percent level.\n"),
1138 N_("\t$t battery time left.\n"),
1139 N_("\t$n battery number.\n"),
1140 N_("\t$o online state (boolean).\n"),
1141 N_("\t$c charging state (boolean).\n"),
1142 "\n",
1143 N_("<h>Mouse Button Actions:\n"),
1144 N_("<b>\tLeft "),
1145 N_(" click on the charging state decal to toggle the display mode\n"
1146 "\t\tbetween a minutes, percentage, or charging rate display.\n"),
1147 N_("<b>\tMiddle "),
1148 N_(" clicking anywhere on the Battery panel also toggles the display mode.\n")
1149 };
1150
1151 static void
create_battery_tab(GtkWidget * tab_vbox)1152 create_battery_tab(GtkWidget *tab_vbox)
1153 {
1154 GtkWidget *tabs, *table, *vbox, *vbox1, *vbox2,
1155 *hbox, *hbox2, *text;
1156 Battery *bat;
1157 gint i;
1158
1159 tabs = gtk_notebook_new();
1160 gtk_notebook_set_tab_pos(GTK_NOTEBOOK(tabs), GTK_POS_TOP);
1161 gtk_box_pack_start(GTK_BOX(tab_vbox), tabs, TRUE, TRUE, 0);
1162
1163 /* -- Setup tab */
1164 vbox = gkrellm_gtk_notebook_page(tabs, _("Options"));
1165 vbox = gkrellm_gtk_framed_vbox(vbox, NULL, 2, TRUE, 10, 6);
1166
1167 if (composite_battery && n_batteries > 0)
1168 {
1169 vbox1 = gkrellm_gtk_category_vbox(vbox, _("Enable"), 2, 2, TRUE);
1170
1171 gkrellm_gtk_check_button_connected(vbox1, NULL,
1172 enable_composite, FALSE, FALSE, 0,
1173 cb_enable, GINT_TO_POINTER(2),
1174 _("Composite Battery"));
1175 gkrellm_gtk_check_button_connected(vbox1, NULL,
1176 enable_each, FALSE, FALSE, 0,
1177 cb_enable, GINT_TO_POINTER(1),
1178 _("Real Batteries"));
1179 }
1180 else
1181 gkrellm_gtk_check_button_connected(vbox, NULL,
1182 enable_each, FALSE, FALSE, 10,
1183 cb_enable, GINT_TO_POINTER(0),
1184 _("Enable Battery"));
1185
1186 vbox2 = gtk_vbox_new(FALSE, 0);
1187 gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);
1188
1189 vbox1 = gtk_vbox_new(FALSE, 0);
1190 gkrellm_gtk_check_button_connected(vbox2, NULL,
1191 enable_estimate, FALSE, FALSE, 2,
1192 cb_enable_estimate, vbox1,
1193 _("Display estimated time remaining and time to charge"));
1194 gtk_widget_set_sensitive(vbox1, enable_estimate ? TRUE : FALSE);
1195 gtk_box_pack_start(GTK_BOX(vbox2), vbox1, FALSE, FALSE, 0);
1196
1197 vbox1 = gkrellm_gtk_category_vbox(vbox1, NULL, 0, 0, TRUE);
1198 gkrellm_gtk_spin_button(vbox1, &estimate_runtime_spin_button[0],
1199 estimate_runtime[0], 0.1, 24, 0.1, 1.0, 1, 55,
1200 cb_runtime, GINT_TO_POINTER(1), FALSE,
1201 _("Total battery run time in hours"));
1202 gkrellm_gtk_spin_button(vbox1, &estimate_runtime_spin_button[1],
1203 estimate_runtime[1], 0.1, 24, 0.1, 1.0, 1, 55,
1204 cb_runtime, GINT_TO_POINTER(2), FALSE,
1205 _("Total battery charge time in hours"));
1206
1207 gkrellm_gtk_check_button_connected(vbox1, &estimate_model_button[1],
1208 estimate_model[1], FALSE, FALSE, 0,
1209 cb_estimate_model, GINT_TO_POINTER(1),
1210 _("Exponential charge model"));
1211
1212 if (!_GK.client_mode)
1213 {
1214 hbox2 = gtk_hbox_new(FALSE, 0);
1215 gkrellm_gtk_spin_button(hbox2, NULL,
1216 (gfloat) poll_interval, 1, 3600, 1, 10, 0, 55,
1217 cb_poll_interval, NULL, FALSE,
1218 _("Seconds between updates"));
1219 gtk_box_pack_end(GTK_BOX(vbox), hbox2, FALSE, FALSE, 6);
1220 }
1221
1222 vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Setup"));
1223
1224 vbox1 = gkrellm_gtk_category_vbox(vbox,
1225 _("Launch Commands"),
1226 4, 0, TRUE);
1227 table = gkrellm_gtk_launcher_table_new(vbox1, 1);
1228 gkrellm_gtk_config_launcher(table, 0, &launch_entry, &tooltip_entry,
1229 _("Battery"), &launch);
1230 g_signal_connect(G_OBJECT(launch_entry), "changed",
1231 G_CALLBACK(cb_launch_entry), NULL);
1232 g_signal_connect(G_OBJECT(tooltip_entry), "changed",
1233 G_CALLBACK(cb_launch_entry), NULL);
1234
1235 hbox = gtk_hbox_new(FALSE, 0);
1236 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, FALSE, 8);
1237 gkrellm_gtk_alert_button(hbox, NULL, FALSE, FALSE, 4, TRUE,
1238 cb_set_alert, NULL);
1239 if (battery_list)
1240 {
1241 bat = (Battery *) battery_list->data;
1242 if (bat && bat->time_left >= 0) /* No choice if no battery times */
1243 gkrellm_gtk_check_button_connected(hbox, NULL,
1244 alert_units_percent, FALSE, FALSE, 16,
1245 alert_units_percent_cb, NULL,
1246 _("Alerts check for percent capacity remaining."));
1247 }
1248
1249
1250 /* --Info tab */
1251 vbox = gkrellm_gtk_framed_notebook_page(tabs, _("Info"));
1252 text = gkrellm_gtk_scrolled_text_view(vbox, NULL,
1253 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1254 for (i = 0; i < sizeof(battery_info_text)/sizeof(gchar *); ++i)
1255 gkrellm_gtk_text_view_append(text, _(battery_info_text[i]));
1256 }
1257
1258 static GkrellmMonitor monitor_battery =
1259 {
1260 N_("Battery"), /* Name, for config tab. */
1261 MON_BATTERY, /* Id, 0 if a plugin */
1262 create_battery, /* The create function */
1263 update_battery, /* The update function */
1264 create_battery_tab, /* The config tab create function */
1265 NULL, /* Apply the config function */
1266
1267 save_battery_config, /* Save user conifg */
1268 load_battery_config, /* Load user config */
1269 BAT_CONFIG_KEYWORD, /* config keyword */
1270
1271 NULL, /* Undef 2 */
1272 NULL, /* Undef 1 */
1273 NULL, /* Undef 0 */
1274
1275 0, /* insert_before_id - place plugin before this mon */
1276
1277 NULL, /* Handle if a plugin, filled in by GKrellM */
1278 NULL /* path if a plugin, filled in by GKrellM */
1279 };
1280
1281 GkrellmMonitor *
gkrellm_init_battery_monitor(void)1282 gkrellm_init_battery_monitor(void)
1283 {
1284 estimate_runtime[0] = 1.5; /* 1.5 hour battery */
1285 estimate_runtime[1] = 3.0; /* 3 hour recharge */
1286 if (_GK.client_mode)
1287 poll_interval = 1;
1288
1289 monitor_battery.name=_(monitor_battery.name);
1290
1291 style_id = gkrellm_add_meter_style(&monitor_battery, BATTERY_STYLE_NAME);
1292 mon_battery = &monitor_battery;
1293 if (setup_battery_interface())
1294 {
1295 (*read_battery_data)();
1296 return &monitor_battery;
1297 }
1298 return NULL;
1299 }
1300