2 * Copyright 2006-2017, Haiku, Inc. All Rights Reserved.
3 * Distributed under the terms of the MIT License.
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
13 #include "PowerStatusView.h"
21 #include <AboutWindow.h>
22 #include <Application.h>
25 #include <ControlLook.h>
31 #include <FindDirectory.h>
33 #include <MessageRunner.h>
34 #include <Notification.h>
36 #include <PopUpMenu.h>
37 #include <Resources.h>
40 #include <TranslationUtils.h>
42 #include "ACPIDriverInterface.h"
43 #include "APMDriverInterface.h"
44 #include "ExtendedInfoWindow.h"
45 #include "PowerStatus.h"
48 #undef B_TRANSLATION_CONTEXT
49 #define B_TRANSLATION_CONTEXT "PowerStatus"
52 extern "C" _EXPORT BView
*instantiate_deskbar_item(void);
53 extern const char* kDeskbarItemName
;
55 const uint32 kMsgToggleLabel
= 'tglb';
56 const uint32 kMsgToggleTime
= 'tgtm';
57 const uint32 kMsgToggleStatusIcon
= 'tgsi';
58 const uint32 kMsgToggleExtInfo
= 'texi';
60 const uint32 kMinIconWidth
= 16;
61 const uint32 kMinIconHeight
= 16;
63 const int32 kLowBatteryPercentage
= 15;
64 const int32 kNoteBatteryPercentage
= 30;
67 PowerStatusView::PowerStatusView(PowerStatusDriverInterface
* interface
,
68 BRect frame
, int32 resizingMode
, int batteryID
, bool inDeskbar
)
70 BView(frame
, kDeskbarItemName
, resizingMode
,
71 B_WILL_DRAW
| B_FULL_UPDATE_ON_RESIZE
),
72 fDriverInterface(interface
),
73 fBatteryID(batteryID
),
80 PowerStatusView::PowerStatusView(BMessage
* archive
)
86 if (be_app
->GetAppInfo(&info
) == B_OK
87 && !strcasecmp(info
.signature
, kDeskbarSignature
))
94 PowerStatusView::~PowerStatusView()
100 PowerStatusView::Archive(BMessage
* archive
, bool deep
) const
102 status_t status
= BView::Archive(archive
, deep
);
104 status
= ToMessage(archive
);
111 PowerStatusView::_Init()
115 fShowStatusIcon
= true;
124 PowerStatusView::AttachedToWindow()
126 BView::AttachedToWindow();
127 if (Parent() != NULL
) {
128 if ((Parent()->Flags() & B_DRAW_ON_CHILDREN
) != 0)
129 SetViewColor(B_TRANSPARENT_COLOR
);
133 SetViewUIColor(B_PANEL_BACKGROUND_COLOR
);
135 if (ViewUIColor() != B_NO_COLOR
)
136 SetLowUIColor(ViewUIColor());
138 SetLowColor(ViewColor());
145 PowerStatusView::DetachedFromWindow()
151 PowerStatusView::MessageReceived(BMessage
*message
)
153 switch (message
->what
) {
159 BView::MessageReceived(message
);
165 PowerStatusView::_DrawBattery(BView
* view
, BRect rect
)
167 BRect lightningRect
= rect
;
168 float quarter
= floorf((rect
.Height() + 1) / 4);
170 rect
.bottom
-= quarter
;
174 float left
= rect
.left
;
175 rect
.left
+= rect
.Width() / 11;
176 lightningRect
.left
= rect
.left
;
177 lightningRect
.InsetBy(0.0f
, 5.0f
* rect
.Height() / 16);
179 if (view
->LowColor().Brightness() > 100)
180 view
->SetHighColor(0, 0, 0);
182 view
->SetHighColor(128, 128, 128);
185 if (rect
.Height() > 8) {
186 gap
= ceilf((rect
.left
- left
) / 2);
189 view
->FillRect(BRect(rect
.left
, rect
.top
, rect
.left
+ gap
- 1, rect
.bottom
));
191 view
->FillRect(BRect(rect
.right
- gap
+ 1, rect
.top
, rect
.right
,
194 view
->FillRect(BRect(rect
.left
+ gap
, rect
.top
, rect
.right
- gap
,
195 rect
.top
+ gap
- 1));
197 view
->FillRect(BRect(rect
.left
+ gap
, rect
.bottom
+ 1 - gap
,
198 rect
.right
- gap
, rect
.bottom
));
200 view
->StrokeRect(rect
);
202 view
->FillRect(BRect(left
, floorf(rect
.top
+ rect
.Height() / 4) + 1,
203 rect
.left
- 1, floorf(rect
.bottom
- rect
.Height() / 4)));
205 int32 percent
= fPercent
;
208 else if (percent
< 0 || !fHasBattery
)
212 rect
.InsetBy(gap
, gap
);
213 rgb_color base
= (rgb_color
){84, 84, 84, 255};
214 if (view
->LowColor().Brightness() < 128)
215 base
= (rgb_color
){172, 172, 172, 255};
217 if (be_control_look
!= NULL
) {
219 if (fHasBattery
&& percent
> 0)
220 empty
.left
+= empty
.Width() * percent
/ 100.0;
222 be_control_look
->DrawButtonBackground(view
, empty
, empty
, base
,
224 ? BControlLook::B_ACTIVATED
: BControlLook::B_DISABLED
,
225 fHasBattery
&& percent
> 0
226 ? (BControlLook::B_ALL_BORDERS
227 & ~BControlLook::B_LEFT_BORDER
)
228 : BControlLook::B_ALL_BORDERS
);
232 if (percent
<= kLowBatteryPercentage
)
233 base
.set_to(180, 0, 0);
234 else if (percent
<= kNoteBatteryPercentage
)
235 base
.set_to(200, 140, 0);
237 base
.set_to(20, 180, 0);
239 rect
.right
= rect
.left
+ rect
.Width() * percent
/ 100.0;
241 if (be_control_look
!= NULL
) {
242 be_control_look
->DrawButtonBackground(view
, rect
, rect
, base
,
243 fHasBattery
? 0 : BControlLook::B_DISABLED
);
245 view
->FillRect(rect
);
250 // When charging, draw a lightning symbol over the battery.
251 view
->SetHighColor(255, 255, 0, 180);
252 view
->SetDrawingMode(B_OP_ALPHA
);
254 static const BPoint points
[] = {
262 view
->FillPolygon(points
, 6, lightningRect
);
264 view
->SetDrawingMode(B_OP_OVER
);
267 view
->SetHighColor(0, 0, 0);
272 PowerStatusView::Draw(BRect updateRect
)
274 DrawTo(this, Bounds());
278 PowerStatusView::DrawTo(BView
* view
, BRect rect
)
280 bool inside
= rect
.Width() >= 40.0f
&& rect
.Height() >= 40.0f
;
282 font_height fontHeight
;
283 view
->GetFontHeight(&fontHeight
);
284 float baseLine
= ceilf(fontHeight
.ascent
);
287 _SetLabel(text
, sizeof(text
));
289 float textHeight
= ceilf(fontHeight
.descent
+ fontHeight
.ascent
);
290 float textWidth
= view
->StringWidth(text
);
291 bool showLabel
= fShowLabel
&& text
[0];
295 if (fShowStatusIcon
) {
299 iconRect
.right
-= textWidth
+ 2;
302 if (iconRect
.Width() + 1 >= kMinIconWidth
303 && iconRect
.Height() + 1 >= kMinIconHeight
) {
304 _DrawBattery(view
, iconRect
);
306 // there is not enough space for the icon
307 iconRect
.Set(0, 0, -1, -1);
312 BPoint
point(0, baseLine
+ rect
.top
);
314 if (iconRect
.IsValid()) {
315 if (inside
== true) {
316 point
.x
= rect
.left
+ (iconRect
.Width() - textWidth
) / 2 +
317 iconRect
.Width() / 20;
318 point
.y
+= (iconRect
.Height() - textHeight
) / 2;
320 point
.x
= rect
.left
+ iconRect
.Width() + 2;
321 point
.y
+= (iconRect
.Height() - textHeight
) / 2;
324 point
.x
= rect
.left
+ (Bounds().Width() - textWidth
) / 2;
325 point
.y
+= (Bounds().Height() - textHeight
) / 2;
328 view
->SetDrawingMode(B_OP_OVER
);
329 if (fInDeskbar
== false || inside
== true) {
330 view
->SetHighUIColor(B_CONTROL_BACKGROUND_COLOR
);
331 view
->DrawString(text
, BPoint(point
.x
+ 1, point
.y
+ 1));
333 view
->SetHighUIColor(B_CONTROL_TEXT_COLOR
);
335 view
->DrawString(text
, point
);
341 PowerStatusView::_SetLabel(char* buffer
, size_t bufferLength
)
343 if (bufferLength
< 1)
351 const char* open
= "";
352 const char* close
= "";
358 if (!fShowTime
&& fPercent
>= 0) {
359 snprintf(buffer
, bufferLength
, "%s%" B_PRId32
"%%%s", open
, fPercent
,
361 } else if (fShowTime
&& fTimeLeft
>= 0) {
362 snprintf(buffer
, bufferLength
, "%s%" B_PRIdTIME
":%02" B_PRIdTIME
"%s",
363 open
, fTimeLeft
/ 3600, (fTimeLeft
/ 60) % 60, close
);
370 PowerStatusView::Update(bool force
)
372 int32 previousPercent
= fPercent
;
373 time_t previousTimeLeft
= fTimeLeft
;
374 bool wasOnline
= fOnline
;
375 bool hadBattery
= fHasBattery
;
376 _GetBatteryInfo(fBatteryID
, &fBatteryInfo
);
377 fHasBattery
= fBatteryInfo
.full_capacity
> 0;
379 if (fBatteryInfo
.full_capacity
> 0 && fHasBattery
) {
380 fPercent
= (100 * fBatteryInfo
.capacity
) / fBatteryInfo
.full_capacity
;
381 fOnline
= (fBatteryInfo
.state
& BATTERY_DISCHARGING
) == 0;
382 fTimeLeft
= fBatteryInfo
.time_left
;
389 if (fHasBattery
&& (fPercent
<= 0 || fPercent
> 100)) {
390 // Just ignore this probe -- it obviously returned invalid values
391 fPercent
= previousPercent
;
392 fTimeLeft
= previousTimeLeft
;
394 fHasBattery
= hadBattery
;
399 // make sure the tray icon is (just) large enough
400 float width
= fShowStatusIcon
? kMinIconWidth
- 1 : 0;
404 _SetLabel(text
, sizeof(text
));
407 width
+= ceilf(StringWidth(text
)) + 2;
410 const char* open
= "";
411 const char* close
= "";
417 size_t length
= snprintf(text
, sizeof(text
), "%s%" B_PRId32
418 "%%%s", open
, fPercent
, close
);
419 if (fTimeLeft
>= 0) {
420 length
+= snprintf(text
+ length
, sizeof(text
) - length
,
421 "\n%" B_PRIdTIME
":%02" B_PRIdTIME
, fTimeLeft
/ 3600,
422 (fTimeLeft
/ 60) % 60);
425 const char* state
= NULL
;
426 if ((fBatteryInfo
.state
& BATTERY_CHARGING
) != 0)
427 state
= B_TRANSLATE("charging");
428 else if ((fBatteryInfo
.state
& BATTERY_DISCHARGING
) != 0)
429 state
= B_TRANSLATE("discharging");
432 snprintf(text
+ length
, sizeof(text
) - length
, "\n%s",
436 strcpy(text
, B_TRANSLATE("no battery"));
440 // make sure we're not going away completely
444 if (width
!= Bounds().Width()) {
445 ResizeTo(width
, Bounds().Height());
447 // inform Deskbar that it needs to realign its replicants
448 BWindow
* window
= Window();
449 if (window
!= NULL
) {
450 BView
* view
= window
->FindView("Status");
452 BMessenger
target((BHandler
*)view
);
453 BMessage
realignReplicants('Algn');
454 target
.SendMessage(&realignReplicants
);
460 if (force
|| wasOnline
!= fOnline
461 || (fShowTime
&& fTimeLeft
!= previousTimeLeft
)
462 || (!fShowTime
&& fPercent
!= previousPercent
)) {
466 if (!fOnline
&& fHasBattery
&& previousPercent
> kLowBatteryPercentage
467 && fPercent
<= kLowBatteryPercentage
) {
474 PowerStatusView::FromMessage(const BMessage
* archive
)
477 if (archive
->FindBool("show label", &value
) == B_OK
)
479 if (archive
->FindBool("show icon", &value
) == B_OK
)
480 fShowStatusIcon
= value
;
481 if (archive
->FindBool("show time", &value
) == B_OK
)
484 //Incase we have a bad saving and none are showed..
485 if (!fShowLabel
&& !fShowStatusIcon
)
489 if (archive
->FindInt32("battery id", &intValue
) == B_OK
)
490 fBatteryID
= intValue
;
495 PowerStatusView::ToMessage(BMessage
* archive
) const
497 status_t status
= archive
->AddBool("show label", fShowLabel
);
499 status
= archive
->AddBool("show icon", fShowStatusIcon
);
501 status
= archive
->AddBool("show time", fShowTime
);
503 status
= archive
->AddInt32("battery id", fBatteryID
);
510 PowerStatusView::_GetBatteryInfo(int batteryID
, battery_info
* batteryInfo
)
512 if (batteryID
>= 0) {
513 fDriverInterface
->GetBatteryInfo(batteryID
, batteryInfo
);
516 memset(batteryInfo
, 0, sizeof(battery_info
));
518 for (int i
= 0; i
< fDriverInterface
->GetBatteryCount(); i
++) {
520 fDriverInterface
->GetBatteryInfo(i
, &info
);
522 if (info
.full_capacity
<= 0)
529 batteryInfo
->state
|= info
.state
;
530 batteryInfo
->capacity
+= info
.capacity
;
531 batteryInfo
->full_capacity
+= info
.full_capacity
;
532 batteryInfo
->time_left
+= info
.time_left
;
540 PowerStatusView::_NotifyLowBattery()
542 BBitmap
* bitmap
= NULL
;
543 BResources resources
;
544 resources
.SetToImage((void*)&instantiate_deskbar_item
);
546 if (resources
.InitCheck() == B_OK
) {
547 size_t resourceSize
= 0;
548 const void* resourceData
= resources
.LoadResource(
549 B_VECTOR_ICON_TYPE
, fHasBattery
550 ? "battery_low" : "battery_critical", &resourceSize
);
551 if (resourceData
!= NULL
) {
552 BMemoryIO
memoryIO(resourceData
, resourceSize
);
553 bitmap
= BTranslationUtils::GetBitmap(&memoryIO
);
557 BNotification
notification(
558 fHasBattery
? B_INFORMATION_NOTIFICATION
: B_ERROR_NOTIFICATION
);
561 notification
.SetTitle(B_TRANSLATE("Battery low"));
562 notification
.SetContent(B_TRANSLATE(
563 "The battery level is getting low, please plug in the device."));
565 notification
.SetTitle(B_TRANSLATE("Battery critical"));
566 notification
.SetContent(B_TRANSLATE(
567 "The battery level is critical, please plug in the device "
571 notification
.SetIcon(bitmap
);
577 // #pragma mark - Replicant view
580 PowerStatusReplicant::PowerStatusReplicant(BRect frame
, int32 resizingMode
,
583 PowerStatusView(NULL
, frame
, resizingMode
, -1, inDeskbar
),
590 // we were obviously added to a standard window - let's add a dragger
591 frame
.OffsetTo(B_ORIGIN
);
592 frame
.top
= frame
.bottom
- 7;
593 frame
.left
= frame
.right
- 7;
594 BDragger
* dragger
= new BDragger(frame
, this,
595 B_FOLLOW_RIGHT
| B_FOLLOW_BOTTOM
);
602 PowerStatusReplicant::PowerStatusReplicant(BMessage
* archive
)
604 PowerStatusView(archive
),
612 PowerStatusReplicant::~PowerStatusReplicant()
615 delete fExtWindowMessenger
;
617 if (fExtendedWindow
!= NULL
&& fExtendedWindow
->Lock()) {
618 fExtendedWindow
->Quit();
619 fExtendedWindow
= NULL
;
622 fDriverInterface
->StopWatching(this);
623 fDriverInterface
->Disconnect();
624 fDriverInterface
->ReleaseReference();
630 PowerStatusReplicant
*
631 PowerStatusReplicant::Instantiate(BMessage
* archive
)
633 if (!validate_instantiation(archive
, "PowerStatusReplicant"))
636 return new PowerStatusReplicant(archive
);
641 PowerStatusReplicant::Archive(BMessage
* archive
, bool deep
) const
643 status_t status
= PowerStatusView::Archive(archive
, deep
);
645 status
= archive
->AddString("add_on", kSignature
);
647 status
= archive
->AddString("class", "PowerStatusReplicant");
654 PowerStatusReplicant::MessageReceived(BMessage
*message
)
656 switch (message
->what
) {
657 case kMsgToggleLabel
:
659 fShowLabel
= !fShowLabel
;
667 fShowTime
= !fShowTime
;
671 case kMsgToggleStatusIcon
:
673 fShowStatusIcon
= !fShowStatusIcon
;
675 fShowStatusIcon
= true;
680 case kMsgToggleExtInfo
:
681 _OpenExtendedWindow();
684 case B_ABOUT_REQUESTED
:
688 case B_QUIT_REQUESTED
:
693 PowerStatusView::MessageReceived(message
);
699 PowerStatusReplicant::MouseDown(BPoint point
)
701 BMessage
* msg
= Window()->CurrentMessage();
702 int32 buttons
= msg
->GetInt32("buttons", 0);
703 if ((buttons
& B_TERTIARY_MOUSE_BUTTON
) != 0) {
704 BMessenger
messenger(this);
705 messenger
.SendMessage(kMsgToggleExtInfo
);
707 BPopUpMenu
* menu
= new BPopUpMenu(B_EMPTY_STRING
, false, false);
708 menu
->SetFont(be_plain_font
);
711 menu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Show text label"),
712 new BMessage(kMsgToggleLabel
)));
714 item
->SetMarked(true);
715 menu
->AddItem(item
= new BMenuItem(B_TRANSLATE("Show status icon"),
716 new BMessage(kMsgToggleStatusIcon
)));
718 item
->SetMarked(true);
719 menu
->AddItem(new BMenuItem(!fShowTime
? B_TRANSLATE("Show time") :
720 B_TRANSLATE("Show percent"), new BMessage(kMsgToggleTime
)));
722 menu
->AddSeparatorItem();
723 menu
->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS
),
724 new BMessage(kMsgToggleExtInfo
)));
726 menu
->AddSeparatorItem();
727 menu
->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS
),
728 new BMessage(B_ABOUT_REQUESTED
)));
729 menu
->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
730 new BMessage(B_QUIT_REQUESTED
)));
731 menu
->SetTargetForItems(this);
733 ConvertToScreen(&point
);
734 menu
->Go(point
, true, false, true);
740 PowerStatusReplicant::_AboutRequested()
742 BAboutWindow
* window
= new BAboutWindow(
743 B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature
);
745 const char* authors
[] = {
747 "Alexander von Gluck",
752 window
->AddCopyright(2006, "Haiku, Inc.");
753 window
->AddAuthors(authors
);
760 PowerStatusReplicant::_Init()
762 fDriverInterface
= new ACPIDriverInterface
;
763 if (fDriverInterface
->Connect() != B_OK
) {
764 delete fDriverInterface
;
765 fDriverInterface
= new APMDriverInterface
;
766 if (fDriverInterface
->Connect() != B_OK
) {
767 fprintf(stderr
, "No power interface found.\n");
772 fExtendedWindow
= NULL
;
773 fMessengerExist
= false;
774 fExtWindowMessenger
= NULL
;
776 fDriverInterface
->StartWatching(this);
781 PowerStatusReplicant::_Quit()
785 deskbar
.RemoveItem(kDeskbarItemName
);
786 } else if (fReplicated
) {
787 BDragger
*dragger
= dynamic_cast<BDragger
*>(ChildAt(0));
788 if (dragger
!= NULL
) {
789 BMessenger
messenger(dragger
);
790 messenger
.SendMessage(new BMessage(B_TRASH_TARGET
));
793 be_app
->PostMessage(B_QUIT_REQUESTED
);
798 PowerStatusReplicant::_GetSettings(BFile
& file
, int mode
)
801 status_t status
= find_directory(B_USER_SETTINGS_DIRECTORY
, &path
,
802 (mode
& O_ACCMODE
) != O_RDONLY
);
806 path
.Append("PowerStatus settings");
808 return file
.SetTo(path
.Path(), mode
);
813 PowerStatusReplicant::_LoadSettings()
818 if (_GetSettings(file
, B_READ_ONLY
) != B_OK
)
822 if (settings
.Unflatten(&file
) < B_OK
)
825 FromMessage(&settings
);
830 PowerStatusReplicant::_SaveSettings()
833 if (_GetSettings(file
, B_WRITE_ONLY
| B_CREATE_FILE
| B_ERASE_FILE
) != B_OK
)
836 BMessage
settings('pwst');
837 ToMessage(&settings
);
840 settings
.Flatten(&file
, &size
);
845 PowerStatusReplicant::_OpenExtendedWindow()
847 if (!fExtendedWindow
) {
848 fExtendedWindow
= new ExtendedInfoWindow(fDriverInterface
);
849 fExtWindowMessenger
= new BMessenger(NULL
, fExtendedWindow
);
850 fExtendedWindow
->Show();
854 BMessage
msg(B_SET_PROPERTY
);
855 msg
.AddSpecifier("Hidden", int32(0));
856 if (fExtWindowMessenger
->SendMessage(&msg
) == B_BAD_PORT_ID
) {
857 fExtendedWindow
= new ExtendedInfoWindow(fDriverInterface
);
859 delete fExtWindowMessenger
;
860 fExtWindowMessenger
= new BMessenger(NULL
, fExtendedWindow
);
861 fMessengerExist
= true;
862 fExtendedWindow
->Show();
864 fExtendedWindow
->Activate();
872 extern "C" _EXPORT BView
*
873 instantiate_deskbar_item(void)
875 return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE
, true);