1 /* 2 * Copyright 2006-2012, 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 */ 10 11 12 #include "PowerStatusView.h" 13 14 #include <stdio.h> 15 #include <stdlib.h> 16 #include <string.h> 17 #include <unistd.h> 18 19 #include <Alert.h> 20 #include <Application.h> 21 #include <Catalog.h> 22 #include <ControlLook.h> 23 #include <Deskbar.h> 24 #include <Dragger.h> 25 #include <Drivers.h> 26 #include <File.h> 27 #include <FindDirectory.h> 28 #include <MenuItem.h> 29 #include <MessageRunner.h> 30 #include <Path.h> 31 #include <PopUpMenu.h> 32 #include <TextView.h> 33 34 #include "ACPIDriverInterface.h" 35 #include "APMDriverInterface.h" 36 #include "ExtendedInfoWindow.h" 37 #include "PowerStatus.h" 38 39 40 #undef B_TRANSLATION_CONTEXT 41 #define B_TRANSLATION_CONTEXT "PowerStatus" 42 43 44 extern "C" _EXPORT BView *instantiate_deskbar_item(void); 45 extern const char* kDeskbarItemName; 46 47 const uint32 kMsgToggleLabel = 'tglb'; 48 const uint32 kMsgToggleTime = 'tgtm'; 49 const uint32 kMsgToggleStatusIcon = 'tgsi'; 50 const uint32 kMsgToggleExtInfo = 'texi'; 51 52 const uint32 kMinIconWidth = 16; 53 const uint32 kMinIconHeight = 16; 54 55 PowerStatusView::PowerStatusView(PowerStatusDriverInterface* interface, 56 BRect frame, int32 resizingMode, int batteryID, bool inDeskbar) 57 : 58 BView(frame, kDeskbarItemName, resizingMode, 59 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 60 fDriverInterface(interface), 61 fBatteryID(batteryID), 62 fInDeskbar(inDeskbar) 63 { 64 fPreferredSize.width = frame.Width(); 65 fPreferredSize.height = frame.Height(); 66 _Init(); 67 } 68 69 70 PowerStatusView::PowerStatusView(BMessage* archive) 71 : BView(archive) 72 { 73 _Init(); 74 FromMessage(archive); 75 } 76 77 78 PowerStatusView::~PowerStatusView() 79 { 80 } 81 82 83 status_t 84 PowerStatusView::Archive(BMessage* archive, bool deep) const 85 { 86 status_t status = BView::Archive(archive, deep); 87 if (status == B_OK) 88 status = ToMessage(archive); 89 90 return status; 91 } 92 93 94 void 95 PowerStatusView::_Init() 96 { 97 SetViewColor(B_TRANSPARENT_COLOR); 98 99 fShowLabel = true; 100 fShowTime = false; 101 fShowStatusIcon = true; 102 103 fPercent = -1; 104 fOnline = true; 105 fTimeLeft = 0; 106 } 107 108 109 void 110 PowerStatusView::AttachedToWindow() 111 { 112 BView::AttachedToWindow(); 113 if (Parent()) 114 SetLowColor(Parent()->ViewColor()); 115 else 116 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 117 118 Update(); 119 } 120 121 122 void 123 PowerStatusView::DetachedFromWindow() 124 { 125 } 126 127 128 void 129 PowerStatusView::MessageReceived(BMessage *message) 130 { 131 switch (message->what) { 132 case kMsgUpdate: 133 Update(); 134 break; 135 136 default: 137 BView::MessageReceived(message); 138 } 139 } 140 141 142 void 143 PowerStatusView::GetPreferredSize(float *width, float *height) 144 { 145 *width = fPreferredSize.width; 146 *height = fPreferredSize.height; 147 } 148 149 150 void 151 PowerStatusView::_DrawBattery(BRect rect) 152 { 153 float quarter = floorf((rect.Height() + 1) / 4); 154 rect.top += quarter; 155 rect.bottom -= quarter; 156 157 rect.InsetBy(2, 0); 158 159 float left = rect.left; 160 rect.left += rect.Width() / 11; 161 162 SetHighColor(0, 0, 0); 163 164 float gap = 1; 165 if (rect.Height() > 8) { 166 gap = ceilf((rect.left - left) / 2); 167 168 // left 169 FillRect(BRect(rect.left, rect.top, rect.left + gap - 1, rect.bottom)); 170 // right 171 FillRect(BRect(rect.right - gap + 1, rect.top, rect.right, 172 rect.bottom)); 173 // top 174 FillRect(BRect(rect.left + gap, rect.top, rect.right - gap, 175 rect.top + gap - 1)); 176 // bottom 177 FillRect(BRect(rect.left + gap, rect.bottom + 1 - gap, 178 rect.right - gap, rect.bottom)); 179 } else 180 StrokeRect(rect); 181 182 FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1, 183 rect.left - 1, floorf(rect.bottom - rect.Height() / 4))); 184 185 int32 percent = fPercent; 186 if (percent > 100) 187 percent = 100; 188 else if (percent < 0 || !fHasBattery) 189 percent = 0; 190 191 if (percent > 0) { 192 rect.InsetBy(gap, gap); 193 rgb_color base = {84, 84, 84, 255}; 194 if (be_control_look != NULL) { 195 BRect empty = rect; 196 if (fHasBattery && percent > 0) 197 empty.left += empty.Width() * percent / 100.0; 198 199 be_control_look->DrawButtonBackground(this, empty, empty, base, 200 fHasBattery 201 ? BControlLook::B_ACTIVATED : BControlLook::B_DISABLED, 202 fHasBattery && percent > 0 203 ? (BControlLook::B_ALL_BORDERS 204 & ~BControlLook::B_LEFT_BORDER) 205 : BControlLook::B_ALL_BORDERS); 206 } 207 208 if (fHasBattery) { 209 if (percent <= 15) 210 base.set_to(180, 0, 0); 211 else 212 base.set_to(20, 180, 0); 213 214 rect.right = rect.left + rect.Width() * percent / 100.0; 215 216 if (be_control_look != NULL) { 217 be_control_look->DrawButtonBackground(this, rect, rect, base, 218 fHasBattery ? 0 : BControlLook::B_DISABLED); 219 } else 220 FillRect(rect); 221 } 222 } 223 224 SetHighColor(0, 0, 0); 225 } 226 227 228 void 229 PowerStatusView::Draw(BRect updateRect) 230 { 231 bool drawBackground = Parent() == NULL 232 || (Parent()->Flags() & B_DRAW_ON_CHILDREN) == 0; 233 if (drawBackground) 234 FillRect(updateRect, B_SOLID_LOW); 235 236 float aspect = Bounds().Width() / Bounds().Height(); 237 bool below = aspect <= 1.0f; 238 239 font_height fontHeight; 240 GetFontHeight(&fontHeight); 241 float baseLine = ceilf(fontHeight.ascent); 242 243 char text[64]; 244 _SetLabel(text, sizeof(text)); 245 246 float textHeight = ceilf(fontHeight.descent + fontHeight.ascent); 247 float textWidth = StringWidth(text); 248 bool showLabel = fShowLabel && text[0]; 249 250 BRect iconRect; 251 252 if (fShowStatusIcon) { 253 iconRect = Bounds(); 254 if (showLabel) { 255 if (below) 256 iconRect.bottom -= textHeight + 4; 257 else 258 iconRect.right -= textWidth + 4; 259 } 260 261 // make a square 262 iconRect.bottom = min_c(iconRect.bottom, iconRect.right); 263 iconRect.right = iconRect.bottom; 264 265 if (iconRect.Width() + 1 >= kMinIconWidth 266 && iconRect.Height() + 1 >= kMinIconHeight) { 267 _DrawBattery(iconRect); 268 } else { 269 // there is not enough space for the icon 270 iconRect.Set(0, 0, -1, -1); 271 } 272 } 273 274 if (showLabel) { 275 BPoint point(0, baseLine); 276 277 if (iconRect.IsValid()) { 278 if (below) { 279 point.x = (iconRect.Width() - textWidth) / 2; 280 point.y += iconRect.Height() + 2; 281 } else { 282 point.x = iconRect.Width() + 2; 283 point.y += (iconRect.Height() - textHeight) / 2; 284 } 285 } else { 286 point.x = (Bounds().Width() - textWidth) / 2; 287 point.y += (Bounds().Height() - textHeight) / 2; 288 } 289 290 if (drawBackground) 291 SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 292 else { 293 SetDrawingMode(B_OP_OVER); 294 rgb_color c = Parent()->LowColor(); 295 if (c.red + c.green + c.blue > 128 * 3) 296 SetHighColor(0, 0, 0); 297 else 298 SetHighColor(255, 255, 255); 299 } 300 301 DrawString(text, point); 302 } 303 } 304 305 306 void 307 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength) 308 { 309 if (bufferLength < 1) 310 return; 311 312 buffer[0] = '\0'; 313 314 if (!fShowLabel) 315 return; 316 317 const char* open = ""; 318 const char* close = ""; 319 if (fOnline) { 320 open = "("; 321 close = ")"; 322 } 323 324 if (!fShowTime && fPercent >= 0) 325 snprintf(buffer, bufferLength, "%s%ld%%%s", open, fPercent, close); 326 else if (fShowTime && fTimeLeft >= 0) { 327 snprintf(buffer, bufferLength, "%s%ld:%02ld%s", 328 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close); 329 } 330 } 331 332 333 334 void 335 PowerStatusView::Update(bool force) 336 { 337 int32 previousPercent = fPercent; 338 bool previousTimeLeft = fTimeLeft; 339 bool wasOnline = fOnline; 340 341 _GetBatteryInfo(&fBatteryInfo, fBatteryID); 342 343 fHasBattery = (fBatteryInfo.state & BATTERY_CRITICAL_STATE) == 0; 344 345 if (fBatteryInfo.full_capacity > 0 && fHasBattery) { 346 fPercent = (100 * fBatteryInfo.capacity) / fBatteryInfo.full_capacity; 347 fOnline = (fBatteryInfo.state & BATTERY_CHARGING) != 0; 348 fTimeLeft = fBatteryInfo.time_left; 349 } else { 350 fPercent = 0; 351 fOnline = false; 352 fTimeLeft = false; 353 } 354 355 356 if (fInDeskbar) { 357 // make sure the tray icon is large enough 358 float width = fShowStatusIcon ? kMinIconWidth + 2 : 0; 359 360 if (fShowLabel) { 361 char text[64]; 362 _SetLabel(text, sizeof(text)); 363 364 if (text[0]) 365 width += ceilf(StringWidth(text)) + 4; 366 } else { 367 char text[256]; 368 const char* open = ""; 369 const char* close = ""; 370 if (fOnline) { 371 open = "("; 372 close = ")"; 373 } 374 if (fHasBattery) { 375 size_t length = snprintf(text, sizeof(text), "%s%ld%%%s", 376 open, fPercent, close); 377 if (fTimeLeft) { 378 length += snprintf(text + length, sizeof(text) - length, 379 "\n%ld:%02ld", fTimeLeft / 3600, (fTimeLeft / 60) % 60); 380 } 381 382 const char* state = NULL; 383 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0) 384 state = B_TRANSLATE("charging"); 385 else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0) 386 state = B_TRANSLATE("discharging"); 387 388 if (state != NULL) { 389 snprintf(text + length, sizeof(text) - length, "\n%s", 390 state); 391 } 392 } else 393 strcpy(text, B_TRANSLATE("no battery")); 394 SetToolTip(text); 395 } 396 if (width == 0) { 397 // make sure we're not going away completely 398 width = 8; 399 } 400 401 if (width != Bounds().Width()) 402 ResizeTo(width, Bounds().Height()); 403 } 404 405 if (force || wasOnline != fOnline 406 || (fShowTime && fTimeLeft != previousTimeLeft) 407 || (!fShowTime && fPercent != previousPercent)) 408 Invalidate(); 409 } 410 411 412 void 413 PowerStatusView::FromMessage(const BMessage* archive) 414 { 415 bool value; 416 if (archive->FindBool("show label", &value) == B_OK) 417 fShowLabel = value; 418 if (archive->FindBool("show icon", &value) == B_OK) 419 fShowStatusIcon = value; 420 if (archive->FindBool("show time", &value) == B_OK) 421 fShowTime = value; 422 423 //Incase we have a bad saving and none are showed.. 424 if (!fShowLabel && !fShowStatusIcon) 425 fShowLabel = true; 426 427 int32 intValue; 428 if (archive->FindInt32("battery id", &intValue) == B_OK) 429 fBatteryID = intValue; 430 } 431 432 433 status_t 434 PowerStatusView::ToMessage(BMessage* archive) const 435 { 436 status_t status = archive->AddBool("show label", fShowLabel); 437 if (status == B_OK) 438 status = archive->AddBool("show icon", fShowStatusIcon); 439 if (status == B_OK) 440 status = archive->AddBool("show time", fShowTime); 441 if (status == B_OK) 442 status = archive->AddInt32("battery id", fBatteryID); 443 444 return status; 445 } 446 447 448 void 449 PowerStatusView::_GetBatteryInfo(battery_info* batteryInfo, int batteryID) 450 { 451 if (batteryID >= 0) { 452 fDriverInterface->GetBatteryInfo(batteryInfo, batteryID); 453 } else { 454 for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) { 455 battery_info info; 456 fDriverInterface->GetBatteryInfo(&info, i); 457 458 if (i == 0) 459 *batteryInfo = info; 460 else { 461 batteryInfo->state &= info.state; 462 batteryInfo->capacity += info.capacity; 463 batteryInfo->full_capacity += info.full_capacity; 464 batteryInfo->time_left += info.time_left; 465 } 466 } 467 } 468 } 469 470 471 // #pragma mark - 472 473 474 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode, 475 bool inDeskbar) 476 : 477 PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar) 478 { 479 _Init(); 480 _LoadSettings(); 481 482 if (!inDeskbar) { 483 // we were obviously added to a standard window - let's add a dragger 484 frame.OffsetTo(B_ORIGIN); 485 frame.top = frame.bottom - 7; 486 frame.left = frame.right - 7; 487 BDragger* dragger = new BDragger(frame, this, 488 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 489 AddChild(dragger); 490 } else 491 Update(); 492 } 493 494 495 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive) 496 : 497 PowerStatusView(archive) 498 { 499 _Init(); 500 _LoadSettings(); 501 } 502 503 504 PowerStatusReplicant::~PowerStatusReplicant() 505 { 506 if (fMessengerExist) { 507 delete fExtWindowMessenger; 508 } 509 510 fDriverInterface->StopWatching(this); 511 fDriverInterface->Disconnect(); 512 fDriverInterface->ReleaseReference(); 513 514 _SaveSettings(); 515 } 516 517 518 PowerStatusReplicant* 519 PowerStatusReplicant::Instantiate(BMessage* archive) 520 { 521 if (!validate_instantiation(archive, "PowerStatusReplicant")) 522 return NULL; 523 524 return new PowerStatusReplicant(archive); 525 } 526 527 528 status_t 529 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const 530 { 531 status_t status = PowerStatusView::Archive(archive, deep); 532 if (status == B_OK) 533 status = archive->AddString("add_on", kSignature); 534 if (status == B_OK) 535 status = archive->AddString("class", "PowerStatusReplicant"); 536 537 return status; 538 } 539 540 541 void 542 PowerStatusReplicant::MessageReceived(BMessage *message) 543 { 544 switch (message->what) { 545 case kMsgToggleLabel: 546 if (fShowStatusIcon) 547 fShowLabel = !fShowLabel; 548 else 549 fShowLabel = true; 550 551 Update(true); 552 break; 553 554 case kMsgToggleTime: 555 fShowTime = !fShowTime; 556 Update(true); 557 break; 558 559 case kMsgToggleStatusIcon: 560 if (fShowLabel) 561 fShowStatusIcon = !fShowStatusIcon; 562 else 563 fShowStatusIcon = true; 564 565 Update(true); 566 break; 567 568 case kMsgToggleExtInfo: 569 _OpenExtendedWindow(); 570 break; 571 572 case B_ABOUT_REQUESTED: 573 _AboutRequested(); 574 break; 575 576 case B_QUIT_REQUESTED: 577 _Quit(); 578 break; 579 580 default: 581 PowerStatusView::MessageReceived(message); 582 } 583 } 584 585 586 void 587 PowerStatusReplicant::MouseDown(BPoint point) 588 { 589 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 590 menu->SetFont(be_plain_font); 591 592 BMenuItem* item; 593 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"), 594 new BMessage(kMsgToggleLabel))); 595 if (fShowLabel) 596 item->SetMarked(true); 597 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"), 598 new BMessage(kMsgToggleStatusIcon))); 599 if (fShowStatusIcon) 600 item->SetMarked(true); 601 menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") : 602 B_TRANSLATE("Show percent"), 603 new BMessage(kMsgToggleTime))); 604 605 menu->AddSeparatorItem(); 606 menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS), 607 new BMessage(kMsgToggleExtInfo))); 608 609 menu->AddSeparatorItem(); 610 menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS), 611 new BMessage(B_ABOUT_REQUESTED))); 612 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 613 new BMessage(B_QUIT_REQUESTED))); 614 menu->SetTargetForItems(this); 615 616 ConvertToScreen(&point); 617 menu->Go(point, true, false, true); 618 } 619 620 621 void 622 PowerStatusReplicant::_AboutRequested() 623 { 624 BAlert* alert = new BAlert(B_TRANSLATE("About"), 625 B_TRANSLATE("PowerStatus\n" 626 "written by Axel Dörfler, Clemens Zeidler\n" 627 "Copyright 2006, Haiku, Inc.\n"), B_TRANSLATE("OK")); 628 BTextView *view = alert->TextView(); 629 BFont font; 630 631 view->SetStylable(true); 632 633 view->GetFont(&font); 634 font.SetSize(18); 635 font.SetFace(B_BOLD_FACE); 636 view->SetFontAndColor(0, strlen(B_TRANSLATE("PowerStatus")), &font); 637 638 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 639 alert->Go(); 640 } 641 642 643 void 644 PowerStatusReplicant::_Init() 645 { 646 fDriverInterface = new ACPIDriverInterface; 647 if (fDriverInterface->Connect() != B_OK) { 648 delete fDriverInterface; 649 fDriverInterface = new APMDriverInterface; 650 if (fDriverInterface->Connect() != B_OK) { 651 fprintf(stderr, "No power interface found.\n"); 652 _Quit(); 653 } 654 } 655 656 fExtendedWindow = NULL; 657 fMessengerExist = false; 658 fExtWindowMessenger = NULL; 659 660 fDriverInterface->StartWatching(this); 661 } 662 663 664 void 665 PowerStatusReplicant::_Quit() 666 { 667 if (fInDeskbar) { 668 BDeskbar deskbar; 669 deskbar.RemoveItem(kDeskbarItemName); 670 } else 671 be_app->PostMessage(B_QUIT_REQUESTED); 672 } 673 674 675 status_t 676 PowerStatusReplicant::_GetSettings(BFile& file, int mode) 677 { 678 BPath path; 679 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, 680 (mode & O_ACCMODE) != O_RDONLY); 681 if (status != B_OK) 682 return status; 683 684 path.Append("PowerStatus settings"); 685 686 return file.SetTo(path.Path(), mode); 687 } 688 689 690 void 691 PowerStatusReplicant::_LoadSettings() 692 { 693 fShowLabel = false; 694 695 BFile file; 696 if (_GetSettings(file, B_READ_ONLY) != B_OK) 697 return; 698 699 BMessage settings; 700 if (settings.Unflatten(&file) < B_OK) 701 return; 702 703 FromMessage(&settings); 704 } 705 706 707 void 708 PowerStatusReplicant::_SaveSettings() 709 { 710 BFile file; 711 if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK) 712 return; 713 714 BMessage settings('pwst'); 715 ToMessage(&settings); 716 717 ssize_t size = 0; 718 settings.Flatten(&file, &size); 719 } 720 721 722 void 723 PowerStatusReplicant::_OpenExtendedWindow() 724 { 725 if (!fExtendedWindow) { 726 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 727 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 728 fExtendedWindow->Show(); 729 return; 730 } 731 732 BMessage msg(B_SET_PROPERTY); 733 msg.AddSpecifier("Hidden", int32(0)); 734 if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) { 735 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 736 if (fMessengerExist) 737 delete fExtWindowMessenger; 738 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 739 fMessengerExist = true; 740 fExtendedWindow->Show(); 741 } else 742 fExtendedWindow->Activate(); 743 744 } 745 746 747 // #pragma mark - 748 749 750 extern "C" _EXPORT BView* 751 instantiate_deskbar_item(void) 752 { 753 return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true); 754 } 755 756