1 /*
2 * Copyright 2006-2018, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Axel Dörfler, axeld@pinc-software.de
7 * Clemens Zeidler, haiku@Clemens-Zeidler.de
8 * Alexander von Gluck, kallisti5@unixzen.com
9 * Kacper Kasper, kacperkasper@gmail.com
10 */
11
12
13 #include "PowerStatusView.h"
14
15 #include <algorithm>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <unistd.h>
20
21 #include <AboutWindow.h>
22 #include <Application.h>
23 #include <Bitmap.h>
24 #include <Beep.h>
25 #include <Catalog.h>
26 #include <DataIO.h>
27 #include <Deskbar.h>
28 #include <Dragger.h>
29 #include <Drivers.h>
30 #include <File.h>
31 #include <FindDirectory.h>
32 #include <GradientLinear.h>
33 #include <MenuItem.h>
34 #include <MessageRunner.h>
35 #include <Notification.h>
36 #include <NumberFormat.h>
37 #include <Path.h>
38 #include <PopUpMenu.h>
39 #include <Resources.h>
40 #include <Roster.h>
41 #include <TextView.h>
42 #include <TranslationUtils.h>
43
44 #include "ACPIDriverInterface.h"
45 #include "APMDriverInterface.h"
46 #include "ExtendedInfoWindow.h"
47 #include "PowerStatus.h"
48
49
50 #undef B_TRANSLATION_CONTEXT
51 #define B_TRANSLATION_CONTEXT "PowerStatus"
52
53
54 extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth,
55 float maxHeight);
56 extern const char* kDeskbarItemName;
57
58 const uint32 kMsgToggleLabel = 'tglb';
59 const uint32 kMsgToggleTime = 'tgtm';
60 const uint32 kMsgToggleStatusIcon = 'tgsi';
61 const uint32 kMsgToggleExtInfo = 'texi';
62
63 const double kLowBatteryPercentage = 0.15;
64 const double kNoteBatteryPercentage = 0.3;
65 const double kFullBatteryPercentage = 1.0;
66
67 const time_t kLowBatteryTimeLeft = 30 * 60;
68
69
PowerStatusView(PowerStatusDriverInterface * interface,BRect frame,int32 resizingMode,int batteryID,bool inDeskbar)70 PowerStatusView::PowerStatusView(PowerStatusDriverInterface* interface,
71 BRect frame, int32 resizingMode, int batteryID, bool inDeskbar)
72 :
73 BView(frame, kDeskbarItemName, resizingMode,
74 B_WILL_DRAW | B_TRANSPARENT_BACKGROUND | B_FULL_UPDATE_ON_RESIZE),
75 fDriverInterface(interface),
76 fBatteryID(batteryID),
77 fInDeskbar(inDeskbar)
78 {
79 _Init();
80 }
81
82
PowerStatusView(BMessage * archive)83 PowerStatusView::PowerStatusView(BMessage* archive)
84 :
85 BView(archive),
86 fInDeskbar(false)
87 {
88 app_info info;
89 if (be_app->GetAppInfo(&info) == B_OK
90 && !strcasecmp(info.signature, kDeskbarSignature))
91 fInDeskbar = true;
92 _Init();
93 FromMessage(archive);
94 }
95
96
~PowerStatusView()97 PowerStatusView::~PowerStatusView()
98 {
99 }
100
101
102 status_t
Archive(BMessage * archive,bool deep) const103 PowerStatusView::Archive(BMessage* archive, bool deep) const
104 {
105 status_t status = BView::Archive(archive, deep);
106 if (status == B_OK)
107 status = ToMessage(archive);
108
109 return status;
110 }
111
112
113 void
_Init()114 PowerStatusView::_Init()
115 {
116 fShowLabel = true;
117 fShowTime = false;
118 fShowStatusIcon = true;
119
120 fPercent = 1.0;
121 fTimeLeft = 0;
122
123 fHasNotifiedLowBattery = false;
124
125 add_system_beep_event("Battery critical");
126 add_system_beep_event("Battery low");
127 add_system_beep_event("Battery charged");
128 }
129
130
131 void
AttachedToWindow()132 PowerStatusView::AttachedToWindow()
133 {
134 BView::AttachedToWindow();
135
136 SetViewColor(B_TRANSPARENT_COLOR);
137
138 if (ViewUIColor() != B_NO_COLOR)
139 SetLowUIColor(ViewUIColor());
140 else
141 SetLowColor(ViewColor());
142
143 Update();
144 }
145
146
147 void
DetachedFromWindow()148 PowerStatusView::DetachedFromWindow()
149 {
150 }
151
152
153 void
MessageReceived(BMessage * message)154 PowerStatusView::MessageReceived(BMessage *message)
155 {
156 switch (message->what) {
157 case kMsgUpdate:
158 Update();
159 break;
160
161 default:
162 BView::MessageReceived(message);
163 break;
164 }
165 }
166
167
168 void
_DrawBattery(BView * view,BRect rect)169 PowerStatusView::_DrawBattery(BView* view, BRect rect)
170 {
171 BRect lightningRect = rect;
172 BRect pauseRect = rect;
173 float quarter = floorf((rect.Height() + 1) / 4);
174 rect.top += quarter;
175 rect.bottom -= quarter;
176
177 rect.InsetBy(2, 0);
178
179 float left = rect.left;
180 rect.left += rect.Width() / 11;
181 lightningRect.left = rect.left;
182 lightningRect.InsetBy(0.0f, 5.0f * rect.Height() / 16);
183 pauseRect.left = rect.left;
184 pauseRect.InsetBy(rect.Width() * 0.1f, rect.Height() * 0.4f);
185
186 if (view->LowColor().IsLight())
187 view->SetHighColor(0, 0, 0);
188 else
189 view->SetHighColor(128, 128, 128);
190
191 float gap = 1;
192 if (rect.Height() > 8) {
193 gap = ceilf((rect.left - left) / 2);
194
195 // left
196 view->FillRect(BRect(rect.left, rect.top, rect.left + gap - 1,
197 rect.bottom));
198 // right
199 view->FillRect(BRect(rect.right - gap + 1, rect.top, rect.right,
200 rect.bottom));
201 // top
202 view->FillRect(BRect(rect.left + gap, rect.top, rect.right - gap,
203 rect.top + gap - 1));
204 // bottom
205 view->FillRect(BRect(rect.left + gap, rect.bottom + 1 - gap,
206 rect.right - gap, rect.bottom));
207 } else
208 view->StrokeRect(rect);
209
210 view->FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1,
211 rect.left - 1, floorf(rect.bottom - rect.Height() / 4)));
212
213 double percent = fPercent;
214 if (percent > 1.0)
215 percent = 1.0;
216 else if (percent < 0.0 || !fHasBattery)
217 percent = 0.0;
218
219 rect.InsetBy(gap, gap);
220
221 if (fHasBattery) {
222 // draw unfilled area
223 rgb_color unfilledColor = make_color(0x4c, 0x4c, 0x4c);
224 if (view->LowColor().IsDark()) {
225 unfilledColor.red = 256 - unfilledColor.red;
226 unfilledColor.green = 256 - unfilledColor.green;
227 unfilledColor.blue = 256 - unfilledColor.blue;
228 }
229
230 BRect unfilled = rect;
231 if (percent > 0.0)
232 unfilled.left += unfilled.Width() * percent;
233
234 view->SetHighColor(unfilledColor);
235 view->FillRect(unfilled);
236
237 if (percent > 0.0) {
238 // draw filled area
239 rgb_color fillColor;
240 if (percent <= kLowBatteryPercentage)
241 fillColor.set_to(180, 0, 0);
242 else if (percent <= kNoteBatteryPercentage)
243 fillColor.set_to(200, 140, 0);
244 else
245 fillColor.set_to(20, 180, 0);
246
247 BRect fill = rect;
248 fill.right = fill.left + fill.Width() * percent;
249
250 // draw bevel
251 rgb_color bevelLightColor = tint_color(fillColor, 0.2);
252 rgb_color bevelShadowColor = tint_color(fillColor, 1.08);
253
254 view->BeginLineArray(4);
255 view->AddLine(BPoint(fill.left, fill.bottom),
256 BPoint(fill.left, fill.top), bevelLightColor);
257 view->AddLine(BPoint(fill.left, fill.top),
258 BPoint(fill.right, fill.top), bevelLightColor);
259 view->AddLine(BPoint(fill.right, fill.top),
260 BPoint(fill.right, fill.bottom), bevelShadowColor);
261 view->AddLine(BPoint(fill.left, fill.bottom),
262 BPoint(fill.right, fill.bottom), bevelShadowColor);
263 view->EndLineArray();
264
265 fill.InsetBy(1, 1);
266
267 // draw gradient
268 float topTint = 0.49;
269 float middleTint1 = 0.62;
270 float middleTint2 = 0.76;
271 float bottomTint = 0.90;
272
273 BGradientLinear gradient;
274 gradient.AddColor(tint_color(fillColor, topTint), 0);
275 gradient.AddColor(tint_color(fillColor, middleTint1), 132);
276 gradient.AddColor(tint_color(fillColor, middleTint2), 136);
277 gradient.AddColor(tint_color(fillColor, bottomTint), 255);
278 gradient.SetStart(fill.LeftTop());
279 gradient.SetEnd(fill.LeftBottom());
280
281 view->FillRect(fill, gradient);
282 }
283 }
284
285 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0) {
286 // When charging, draw a lightning symbol over the battery.
287 view->SetHighColor(255, 255, 0, 180);
288 view->SetDrawingMode(B_OP_ALPHA);
289
290 static const BPoint points[] = {
291 BPoint(3, 14),
292 BPoint(10, 6),
293 BPoint(10, 8),
294 BPoint(17, 3),
295 BPoint(9, 12),
296 BPoint(9, 10)
297 };
298 view->FillPolygon(points, 6, lightningRect);
299
300 view->SetDrawingMode(B_OP_OVER);
301 } else if ((fBatteryInfo.state
302 & (BATTERY_CHARGING | BATTERY_DISCHARGING | BATTERY_CRITICAL_STATE)) == 0) {
303 // When a battery is not in use at all, draw a pause symbol over the battery
304 view->SetHighColor(0, 0, 0, 96);
305 view->SetDrawingMode(B_OP_ALPHA);
306
307 static const BPoint points[] = {
308 BPoint(1, 3),
309 BPoint(1, 6),
310 BPoint(8, 6),
311 BPoint(8, 3),
312
313 BPoint(14, 3),
314 BPoint(14, 6),
315 BPoint(22, 6),
316 BPoint(22, 3)
317 };
318 view->FillPolygon(points, 8, pauseRect);
319
320 view->SetDrawingMode(B_OP_OVER);
321 } else if ((fBatteryInfo.state & BATTERY_CRITICAL_STATE) != 0) {
322 // When a battery is damaged or missing, draw an X over it
323 view->SetHighColor(200, 0, 0, 96);
324 view->SetDrawingMode(B_OP_ALPHA);
325
326 static const BPoint points[] = {
327 BPoint(1, 1),
328 BPoint(1, 2),
329 BPoint(20, 6),
330 BPoint(22, 6),
331 BPoint(22, 5),
332 BPoint(3, 1),
333
334 BPoint(20, 1),
335 BPoint(1, 5),
336 BPoint(1, 6),
337 BPoint(3, 6),
338 BPoint(22, 2),
339 BPoint(22, 1)
340 };
341 view->FillPolygon(points, 12, lightningRect);
342
343 view->SetDrawingMode(B_OP_OVER);
344 }
345
346 view->SetHighColor(0, 0, 0);
347 }
348
349
350 void
Draw(BRect updateRect)351 PowerStatusView::Draw(BRect updateRect)
352 {
353 DrawTo(this, Bounds());
354 }
355
356
357 void
DrawTo(BView * view,BRect rect)358 PowerStatusView::DrawTo(BView* view, BRect rect)
359 {
360 bool inside = rect.Width() >= 40.0f && rect.Height() >= 40.0f;
361
362 font_height fontHeight;
363 view->GetFontHeight(&fontHeight);
364 float baseLine = ceilf(fontHeight.ascent);
365
366 char text[64];
367 _SetLabel(text, sizeof(text));
368
369 float textHeight = ceilf(fontHeight.descent + fontHeight.ascent);
370 float textWidth = view->StringWidth(text);
371 bool showLabel = fShowLabel && text[0];
372
373 BRect iconRect;
374
375 if (fShowStatusIcon) {
376 iconRect = rect;
377 if (showLabel && inside == false)
378 iconRect.right -= textWidth + 2;
379
380 _DrawBattery(view, iconRect);
381 }
382
383 if (showLabel) {
384 BPoint point(0, baseLine + rect.top);
385
386 if (iconRect.IsValid()) {
387 if (inside == true) {
388 point.x = rect.left + (iconRect.Width() - textWidth) / 2 +
389 iconRect.Width() / 20;
390 point.y += (iconRect.Height() - textHeight) / 2;
391 } else {
392 point.x = rect.left + iconRect.Width() + 2;
393 point.y += (iconRect.Height() - textHeight) / 2;
394 }
395 } else {
396 point.x = rect.left + (Bounds().Width() - textWidth) / 2;
397 point.y += (Bounds().Height() - textHeight) / 2;
398 }
399
400 view->SetDrawingMode(B_OP_OVER);
401 if (fInDeskbar == false || inside == true) {
402 view->SetHighUIColor(B_CONTROL_BACKGROUND_COLOR);
403 view->DrawString(text, BPoint(point.x + 1, point.y + 1));
404 }
405 view->SetHighUIColor(B_CONTROL_TEXT_COLOR);
406
407 view->DrawString(text, point);
408 }
409 }
410
411
412 void
_SetLabel(char * buffer,size_t bufferLength)413 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength)
414 {
415 if (bufferLength < 1)
416 return;
417
418 buffer[0] = '\0';
419
420 if (!fShowLabel)
421 return;
422
423 const char* open = "";
424 const char* close = "";
425 if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
426 // surround the percentage with () if the battery is not discharging
427 open = "(";
428 close = ")";
429 }
430
431 if (!fShowTime && fPercent >= 0) {
432 BNumberFormat numberFormat;
433 BString data;
434
435 if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
436 data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
437 }
438
439 snprintf(buffer, bufferLength, "%s%s%s", open, data.String(), close);
440 } else if (fShowTime && fTimeLeft >= 0) {
441 snprintf(buffer, bufferLength, "%s%" B_PRIdTIME ":%02" B_PRIdTIME "%s",
442 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close);
443 }
444 }
445
446
447 void
Update(bool force,bool notify)448 PowerStatusView::Update(bool force, bool notify)
449 {
450 double previousPercent = fPercent;
451 time_t previousTimeLeft = fTimeLeft;
452 bool wasCharging = (fBatteryInfo.state & BATTERY_CHARGING);
453 bool hadBattery = fHasBattery;
454 _GetBatteryInfo(fBatteryID, &fBatteryInfo);
455 fHasBattery = fBatteryInfo.full_capacity > 0 && fBatteryInfo.state != BATTERY_CRITICAL_STATE;
456
457 if (fBatteryInfo.full_capacity > 0 && fHasBattery) {
458 fPercent = (double)fBatteryInfo.capacity / fBatteryInfo.full_capacity;
459 fTimeLeft = fBatteryInfo.time_left;
460 } else {
461 fPercent = 0.0;
462 fTimeLeft = -1;
463 }
464
465 if (fHasBattery && (fPercent <= 0 || fPercent > 1.0)) {
466 // Just ignore this probe -- it obviously returned invalid values
467 fPercent = previousPercent;
468 fTimeLeft = previousTimeLeft;
469 fHasBattery = hadBattery;
470 return;
471 }
472
473 if (fInDeskbar) {
474 // make sure the tray icon is (just) large enough
475 float width = fShowStatusIcon ? Bounds().Height() : 0;
476
477 if (fShowLabel) {
478 char text[64];
479 _SetLabel(text, sizeof(text));
480
481 if (text[0])
482 width += ceilf(StringWidth(text)) + 2;
483 } else {
484 char text[256];
485 const char* open = "";
486 const char* close = "";
487 if ((fBatteryInfo.state & BATTERY_DISCHARGING) == 0) {
488 // surround the percentage with () if the battery is not discharging
489 open = "(";
490 close = ")";
491 }
492 if (fHasBattery) {
493 BNumberFormat numberFormat;
494 BString data;
495 size_t length;
496
497 if (numberFormat.FormatPercent(data, fPercent) != B_OK) {
498 data.SetToFormat("%" B_PRId32 "%%", int32(fPercent * 100));
499 }
500
501 length = snprintf(text, sizeof(text), "%s%s%s", open, data.String(), close);
502
503 if (fTimeLeft >= 0) {
504 length += snprintf(text + length, sizeof(text) - length, "\n%" B_PRIdTIME
505 ":%02" B_PRIdTIME, fTimeLeft / 3600, (fTimeLeft / 60) % 60);
506 }
507
508 const char* state = NULL;
509 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0)
510 state = B_TRANSLATE("charging");
511 else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0)
512 state = B_TRANSLATE("discharging");
513 else if ((fBatteryInfo.state & BATTERY_NOT_CHARGING) != 0)
514 state = B_TRANSLATE("not charging");
515
516 if (state != NULL) {
517 snprintf(text + length, sizeof(text) - length, "\n%s",
518 state);
519 }
520 } else
521 strcpy(text, B_TRANSLATE("no battery"));
522 SetToolTip(text);
523 }
524 if (width < 8) {
525 // make sure we're not going away completely
526 width = 8;
527 }
528
529 if (width != Bounds().Width()) {
530 ResizeTo(width, Bounds().Height());
531
532 // inform Deskbar that it needs to realign its replicants
533 BWindow* window = Window();
534 if (window != NULL) {
535 BView* view = window->FindView("Status");
536 if (view != NULL) {
537 BMessenger target((BHandler*)view);
538 BMessage realignReplicants('Algn');
539 target.SendMessage(&realignReplicants);
540 }
541 }
542 }
543 }
544
545 if (force || wasCharging != (fBatteryInfo.state & BATTERY_CHARGING)
546 || (fShowTime && fTimeLeft != previousTimeLeft)
547 || (!fShowTime && fPercent != previousPercent)) {
548 Invalidate();
549 }
550
551 // only do low battery notices based on the aggregate virtual battery, not single batteries
552 if (fBatteryID >= 0)
553 return;
554
555 if (fPercent > kLowBatteryPercentage && fTimeLeft > kLowBatteryTimeLeft)
556 fHasNotifiedLowBattery = false;
557
558 bool justTurnedLowBattery = (previousPercent > kLowBatteryPercentage
559 && fPercent <= kLowBatteryPercentage)
560 || (fTimeLeft <= kLowBatteryTimeLeft
561 && previousTimeLeft > kLowBatteryTimeLeft);
562
563 if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0 && notify && fHasBattery
564 && !fHasNotifiedLowBattery && justTurnedLowBattery) {
565 _NotifyLowBattery();
566 fHasNotifiedLowBattery = true;
567 }
568
569 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0 && fPercent >= kFullBatteryPercentage
570 && previousPercent < kFullBatteryPercentage) {
571 system_beep("Battery charged");
572 }
573 }
574
575
576 void
FromMessage(const BMessage * archive)577 PowerStatusView::FromMessage(const BMessage* archive)
578 {
579 bool value;
580 if (archive->FindBool("show label", &value) == B_OK)
581 fShowLabel = value;
582 if (archive->FindBool("show icon", &value) == B_OK)
583 fShowStatusIcon = value;
584 if (archive->FindBool("show time", &value) == B_OK)
585 fShowTime = value;
586
587 //Incase we have a bad saving and none are showed..
588 if (!fShowLabel && !fShowStatusIcon)
589 fShowLabel = true;
590
591 int32 intValue;
592 if (archive->FindInt32("battery id", &intValue) == B_OK)
593 fBatteryID = intValue;
594 }
595
596
597 status_t
ToMessage(BMessage * archive) const598 PowerStatusView::ToMessage(BMessage* archive) const
599 {
600 status_t status = archive->AddBool("show label", fShowLabel);
601 if (status == B_OK)
602 status = archive->AddBool("show icon", fShowStatusIcon);
603 if (status == B_OK)
604 status = archive->AddBool("show time", fShowTime);
605 if (status == B_OK)
606 status = archive->AddInt32("battery id", fBatteryID);
607
608 return status;
609 }
610
611
612 void
_GetBatteryInfo(int batteryID,battery_info * batteryInfo)613 PowerStatusView::_GetBatteryInfo(int batteryID, battery_info* batteryInfo)
614 {
615 if (batteryID >= 0) {
616 fDriverInterface->GetBatteryInfo(batteryID, batteryInfo);
617 } else {
618 bool first = true;
619 memset(batteryInfo, 0, sizeof(battery_info));
620
621 for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) {
622 battery_info info;
623 fDriverInterface->GetBatteryInfo(i, &info);
624 if (info.full_capacity <= 0)
625 continue;
626
627 if (first) {
628 *batteryInfo = info;
629 first = false;
630 } else {
631 if ((batteryInfo->state & BATTERY_CRITICAL_STATE) == 0) {
632 // don't propagate CRITICAL_STATE to the aggregate battery.
633 // one battery charging means "the system is charging" but one battery having
634 // been removed does not mean "the system has no battery"
635 batteryInfo->state |= info.state;
636 }
637 batteryInfo->capacity += info.capacity;
638 batteryInfo->full_capacity += info.full_capacity;
639 batteryInfo->current_rate += info.current_rate;
640 }
641 }
642
643 // we can't rely on just adding the individual batteries' time_lefts together:
644 // not-in-use batteries show -1 time_left as they will last infinitely long with their
645 // current (zero) level of draw, despite them being in the queue to use after the current
646 // battery is out of energy. therefore to calculate an accurate time, we have to use the
647 // current total rate of (dis)charge compared to the total remaining capacity of all
648 // batteries.
649 if (batteryInfo->current_rate == 0) {
650 // some systems briefly return current_rate of 0 as the charger is plugged/unplugged
651 batteryInfo->time_left = 0;
652 } else if ((batteryInfo->state & BATTERY_CHARGING) != 0) {
653 batteryInfo->time_left = 3600 * (batteryInfo->full_capacity - batteryInfo->capacity)
654 / batteryInfo->current_rate;
655 } else {
656 batteryInfo->time_left = 3600 * batteryInfo->capacity / batteryInfo->current_rate;
657 }
658 }
659 }
660
661
662 void
_NotifyLowBattery()663 PowerStatusView::_NotifyLowBattery()
664 {
665 BBitmap* bitmap = NULL;
666 BResources resources;
667 resources.SetToImage((void*)&instantiate_deskbar_item);
668
669 if (resources.InitCheck() == B_OK) {
670 size_t resourceSize = 0;
671 const void* resourceData = resources.LoadResource(
672 B_VECTOR_ICON_TYPE, fHasBattery
673 ? "battery_low" : "battery_critical", &resourceSize);
674 if (resourceData != NULL) {
675 BMemoryIO memoryIO(resourceData, resourceSize);
676 bitmap = BTranslationUtils::GetBitmap(&memoryIO);
677 }
678 }
679
680 BNotification notification(
681 fHasBattery ? B_INFORMATION_NOTIFICATION : B_ERROR_NOTIFICATION);
682
683 if (fHasBattery) {
684 system_beep("Battery low");
685 notification.SetTitle(B_TRANSLATE("Battery low"));
686 notification.SetContent(B_TRANSLATE(
687 "The battery level is getting low, please plug in the device."));
688 } else {
689 system_beep("Battery critical");
690 notification.SetTitle(B_TRANSLATE("Battery critical"));
691 notification.SetContent(B_TRANSLATE(
692 "The battery level is critical, please plug in the device "
693 "immediately."));
694 }
695
696 notification.SetIcon(bitmap);
697 notification.Send();
698 delete bitmap;
699 }
700
701
702 // #pragma mark - Replicant view
703
704
PowerStatusReplicant(BRect frame,int32 resizingMode,bool inDeskbar)705 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode,
706 bool inDeskbar)
707 :
708 PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar),
709 fReplicated(false)
710 {
711 _Init();
712 _LoadSettings();
713
714 if (!inDeskbar) {
715 // we were obviously added to a standard window - let's add a dragger
716 frame.OffsetTo(B_ORIGIN);
717 frame.top = frame.bottom - 7;
718 frame.left = frame.right - 7;
719 BDragger* dragger = new BDragger(frame, this,
720 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
721 AddChild(dragger);
722 } else
723 Update(false,false);
724 }
725
726
PowerStatusReplicant(BMessage * archive)727 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive)
728 :
729 PowerStatusView(archive),
730 fReplicated(true)
731 {
732 _Init();
733 _LoadSettings();
734 }
735
736
~PowerStatusReplicant()737 PowerStatusReplicant::~PowerStatusReplicant()
738 {
739 if (fMessengerExist)
740 delete fExtWindowMessenger;
741
742 if (fExtendedWindow != NULL && fExtendedWindow->Lock()) {
743 fExtendedWindow->Quit();
744 fExtendedWindow = NULL;
745 }
746
747 fDriverInterface->StopWatching(this);
748 fDriverInterface->Disconnect();
749 fDriverInterface->ReleaseReference();
750
751 _SaveSettings();
752 }
753
754
755 PowerStatusReplicant*
Instantiate(BMessage * archive)756 PowerStatusReplicant::Instantiate(BMessage* archive)
757 {
758 if (!validate_instantiation(archive, "PowerStatusReplicant"))
759 return NULL;
760
761 return new PowerStatusReplicant(archive);
762 }
763
764
765 status_t
Archive(BMessage * archive,bool deep) const766 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const
767 {
768 status_t status = PowerStatusView::Archive(archive, deep);
769 if (status == B_OK)
770 status = archive->AddString("add_on", kSignature);
771 if (status == B_OK)
772 status = archive->AddString("class", "PowerStatusReplicant");
773
774 return status;
775 }
776
777
778 void
MessageReceived(BMessage * message)779 PowerStatusReplicant::MessageReceived(BMessage *message)
780 {
781 switch (message->what) {
782 case kMsgToggleLabel:
783 if (fShowStatusIcon)
784 fShowLabel = !fShowLabel;
785 else
786 fShowLabel = true;
787
788 Update(true);
789 break;
790
791 case kMsgToggleTime:
792 fShowTime = !fShowTime;
793 Update(true);
794 break;
795
796 case kMsgToggleStatusIcon:
797 if (fShowLabel)
798 fShowStatusIcon = !fShowStatusIcon;
799 else
800 fShowStatusIcon = true;
801
802 Update(true);
803 break;
804
805 case kMsgToggleExtInfo:
806 _OpenExtendedWindow();
807 break;
808
809 case B_ABOUT_REQUESTED:
810 _AboutRequested();
811 break;
812
813 case B_QUIT_REQUESTED:
814 _Quit();
815 break;
816
817 default:
818 PowerStatusView::MessageReceived(message);
819 break;
820 }
821 }
822
823
824 void
MouseDown(BPoint point)825 PowerStatusReplicant::MouseDown(BPoint point)
826 {
827 BMessage* msg = Window()->CurrentMessage();
828 int32 buttons = msg->GetInt32("buttons", 0);
829 if ((buttons & B_TERTIARY_MOUSE_BUTTON) != 0) {
830 BMessenger messenger(this);
831 messenger.SendMessage(kMsgToggleExtInfo);
832 } else {
833 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
834 menu->SetFont(be_plain_font);
835
836 BMenuItem* item;
837 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"),
838 new BMessage(kMsgToggleLabel)));
839 if (fShowLabel)
840 item->SetMarked(true);
841 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"),
842 new BMessage(kMsgToggleStatusIcon)));
843 if (fShowStatusIcon)
844 item->SetMarked(true);
845 menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") :
846 B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime)));
847
848 menu->AddSeparatorItem();
849 menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS),
850 new BMessage(kMsgToggleExtInfo)));
851
852 menu->AddSeparatorItem();
853 menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS),
854 new BMessage(B_ABOUT_REQUESTED)));
855 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
856 new BMessage(B_QUIT_REQUESTED)));
857 menu->SetTargetForItems(this);
858
859 ConvertToScreen(&point);
860 menu->Go(point, true, false, true);
861 }
862 }
863
864
865 void
_AboutRequested()866 PowerStatusReplicant::_AboutRequested()
867 {
868 BAboutWindow* window = new BAboutWindow(
869 B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature);
870
871 const char* authors[] = {
872 "Axel Dörfler",
873 "Alexander von Gluck",
874 "Clemens Zeidler",
875 NULL
876 };
877
878 window->AddCopyright(2006, "Haiku, Inc.");
879 window->AddAuthors(authors);
880
881 window->Show();
882 }
883
884
885 void
_Init()886 PowerStatusReplicant::_Init()
887 {
888 fDriverInterface = new ACPIDriverInterface;
889 if (fDriverInterface->Connect() != B_OK) {
890 delete fDriverInterface;
891 fDriverInterface = new APMDriverInterface;
892 if (fDriverInterface->Connect() != B_OK) {
893 fprintf(stderr, "No power interface found.\n");
894 _Quit();
895 }
896 }
897
898 fExtendedWindow = NULL;
899 fMessengerExist = false;
900 fExtWindowMessenger = NULL;
901
902 fDriverInterface->StartWatching(this);
903 }
904
905
906 void
_Quit()907 PowerStatusReplicant::_Quit()
908 {
909 if (fInDeskbar) {
910 BDeskbar deskbar;
911 deskbar.RemoveItem(kDeskbarItemName);
912 } else if (fReplicated) {
913 BDragger *dragger = dynamic_cast<BDragger*>(ChildAt(0));
914 if (dragger != NULL) {
915 BMessenger messenger(dragger);
916 messenger.SendMessage(new BMessage(B_TRASH_TARGET));
917 }
918 } else
919 be_app->PostMessage(B_QUIT_REQUESTED);
920 }
921
922
923 status_t
_GetSettings(BFile & file,int mode)924 PowerStatusReplicant::_GetSettings(BFile& file, int mode)
925 {
926 BPath path;
927 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path,
928 (mode & O_ACCMODE) != O_RDONLY);
929 if (status != B_OK)
930 return status;
931
932 path.Append("PowerStatus settings");
933
934 return file.SetTo(path.Path(), mode);
935 }
936
937
938 void
_LoadSettings()939 PowerStatusReplicant::_LoadSettings()
940 {
941 fShowLabel = false;
942
943 BFile file;
944 if (_GetSettings(file, B_READ_ONLY) != B_OK)
945 return;
946
947 BMessage settings;
948 if (settings.Unflatten(&file) < B_OK)
949 return;
950
951 FromMessage(&settings);
952 }
953
954
955 void
_SaveSettings()956 PowerStatusReplicant::_SaveSettings()
957 {
958 BFile file;
959 if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK)
960 return;
961
962 BMessage settings('pwst');
963 ToMessage(&settings);
964
965 ssize_t size = 0;
966 settings.Flatten(&file, &size);
967 }
968
969
970 void
_OpenExtendedWindow()971 PowerStatusReplicant::_OpenExtendedWindow()
972 {
973 if (!fExtendedWindow) {
974 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
975 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
976 fExtendedWindow->Show();
977 return;
978 }
979
980 BMessage msg(B_SET_PROPERTY);
981 msg.AddSpecifier("Hidden", int32(0));
982 if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) {
983 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface);
984 if (fMessengerExist)
985 delete fExtWindowMessenger;
986 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow);
987 fMessengerExist = true;
988 fExtendedWindow->Show();
989 } else
990 fExtendedWindow->Activate();
991
992 }
993
994
995 // #pragma mark -
996
997
998 extern "C" _EXPORT BView*
instantiate_deskbar_item(float maxWidth,float maxHeight)999 instantiate_deskbar_item(float maxWidth, float maxHeight)
1000 {
1001 return new PowerStatusReplicant(BRect(0, 0, maxHeight - 1, maxHeight - 1),
1002 B_FOLLOW_NONE, true);
1003 }
1004