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 <AboutWindow.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%" B_PRId32 "%%%s", open, fPercent, 326 close); 327 } else if (fShowTime && fTimeLeft >= 0) { 328 snprintf(buffer, bufferLength, "%s%" B_PRId32 ":%02" B_PRId32 "%s", 329 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close); 330 } 331 } 332 333 334 335 void 336 PowerStatusView::Update(bool force) 337 { 338 int32 previousPercent = fPercent; 339 time_t previousTimeLeft = fTimeLeft; 340 bool wasOnline = fOnline; 341 342 _GetBatteryInfo(&fBatteryInfo, fBatteryID); 343 344 fHasBattery = (fBatteryInfo.state & BATTERY_CRITICAL_STATE) == 0; 345 346 if (fBatteryInfo.full_capacity > 0 && fHasBattery) { 347 fPercent = (100 * fBatteryInfo.capacity) / fBatteryInfo.full_capacity; 348 fOnline = (fBatteryInfo.state & BATTERY_CHARGING) != 0; 349 fTimeLeft = fBatteryInfo.time_left; 350 } else { 351 fPercent = 0; 352 fOnline = false; 353 fTimeLeft = -1; 354 } 355 356 357 if (fInDeskbar) { 358 // make sure the tray icon is large enough 359 float width = fShowStatusIcon ? kMinIconWidth + 2 : 0; 360 361 if (fShowLabel) { 362 char text[64]; 363 _SetLabel(text, sizeof(text)); 364 365 if (text[0]) 366 width += ceilf(StringWidth(text)) + 4; 367 } else { 368 char text[256]; 369 const char* open = ""; 370 const char* close = ""; 371 if (fOnline) { 372 open = "("; 373 close = ")"; 374 } 375 if (fHasBattery) { 376 size_t length = snprintf(text, sizeof(text), "%s%" B_PRId32 377 "%%%s", open, fPercent, close); 378 if (fTimeLeft >= 0) { 379 length += snprintf(text + length, sizeof(text) - length, 380 "\n%" B_PRId32 ":%02" B_PRId32, fTimeLeft / 3600, 381 (fTimeLeft / 60) % 60); 382 } 383 384 const char* state = NULL; 385 if ((fBatteryInfo.state & BATTERY_CHARGING) != 0) 386 state = B_TRANSLATE("charging"); 387 else if ((fBatteryInfo.state & BATTERY_DISCHARGING) != 0) 388 state = B_TRANSLATE("discharging"); 389 390 if (state != NULL) { 391 snprintf(text + length, sizeof(text) - length, "\n%s", 392 state); 393 } 394 } else 395 strcpy(text, B_TRANSLATE("no battery")); 396 SetToolTip(text); 397 } 398 if (width == 0) { 399 // make sure we're not going away completely 400 width = 8; 401 } 402 403 if (width != Bounds().Width()) 404 ResizeTo(width, Bounds().Height()); 405 } 406 407 if (force || wasOnline != fOnline 408 || (fShowTime && fTimeLeft != previousTimeLeft) 409 || (!fShowTime && fPercent != previousPercent)) 410 Invalidate(); 411 } 412 413 414 void 415 PowerStatusView::FromMessage(const BMessage* archive) 416 { 417 bool value; 418 if (archive->FindBool("show label", &value) == B_OK) 419 fShowLabel = value; 420 if (archive->FindBool("show icon", &value) == B_OK) 421 fShowStatusIcon = value; 422 if (archive->FindBool("show time", &value) == B_OK) 423 fShowTime = value; 424 425 //Incase we have a bad saving and none are showed.. 426 if (!fShowLabel && !fShowStatusIcon) 427 fShowLabel = true; 428 429 int32 intValue; 430 if (archive->FindInt32("battery id", &intValue) == B_OK) 431 fBatteryID = intValue; 432 } 433 434 435 status_t 436 PowerStatusView::ToMessage(BMessage* archive) const 437 { 438 status_t status = archive->AddBool("show label", fShowLabel); 439 if (status == B_OK) 440 status = archive->AddBool("show icon", fShowStatusIcon); 441 if (status == B_OK) 442 status = archive->AddBool("show time", fShowTime); 443 if (status == B_OK) 444 status = archive->AddInt32("battery id", fBatteryID); 445 446 return status; 447 } 448 449 450 void 451 PowerStatusView::_GetBatteryInfo(battery_info* batteryInfo, int batteryID) 452 { 453 if (batteryID >= 0) { 454 fDriverInterface->GetBatteryInfo(batteryInfo, batteryID); 455 } else { 456 for (int i = 0; i < fDriverInterface->GetBatteryCount(); i++) { 457 battery_info info; 458 fDriverInterface->GetBatteryInfo(&info, i); 459 460 if (i == 0) 461 *batteryInfo = info; 462 else { 463 batteryInfo->state &= info.state; 464 batteryInfo->capacity += info.capacity; 465 batteryInfo->full_capacity += info.full_capacity; 466 batteryInfo->time_left += info.time_left; 467 } 468 } 469 } 470 } 471 472 473 // #pragma mark - 474 475 476 PowerStatusReplicant::PowerStatusReplicant(BRect frame, int32 resizingMode, 477 bool inDeskbar) 478 : 479 PowerStatusView(NULL, frame, resizingMode, -1, inDeskbar) 480 { 481 _Init(); 482 _LoadSettings(); 483 484 if (!inDeskbar) { 485 // we were obviously added to a standard window - let's add a dragger 486 frame.OffsetTo(B_ORIGIN); 487 frame.top = frame.bottom - 7; 488 frame.left = frame.right - 7; 489 BDragger* dragger = new BDragger(frame, this, 490 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 491 AddChild(dragger); 492 } else 493 Update(); 494 } 495 496 497 PowerStatusReplicant::PowerStatusReplicant(BMessage* archive) 498 : 499 PowerStatusView(archive) 500 { 501 _Init(); 502 _LoadSettings(); 503 } 504 505 506 PowerStatusReplicant::~PowerStatusReplicant() 507 { 508 if (fMessengerExist) 509 delete fExtWindowMessenger; 510 511 fDriverInterface->StopWatching(this); 512 fDriverInterface->Disconnect(); 513 fDriverInterface->ReleaseReference(); 514 515 _SaveSettings(); 516 } 517 518 519 PowerStatusReplicant* 520 PowerStatusReplicant::Instantiate(BMessage* archive) 521 { 522 if (!validate_instantiation(archive, "PowerStatusReplicant")) 523 return NULL; 524 525 return new PowerStatusReplicant(archive); 526 } 527 528 529 status_t 530 PowerStatusReplicant::Archive(BMessage* archive, bool deep) const 531 { 532 status_t status = PowerStatusView::Archive(archive, deep); 533 if (status == B_OK) 534 status = archive->AddString("add_on", kSignature); 535 if (status == B_OK) 536 status = archive->AddString("class", "PowerStatusReplicant"); 537 538 return status; 539 } 540 541 542 void 543 PowerStatusReplicant::MessageReceived(BMessage *message) 544 { 545 switch (message->what) { 546 case kMsgToggleLabel: 547 if (fShowStatusIcon) 548 fShowLabel = !fShowLabel; 549 else 550 fShowLabel = true; 551 552 Update(true); 553 break; 554 555 case kMsgToggleTime: 556 fShowTime = !fShowTime; 557 Update(true); 558 break; 559 560 case kMsgToggleStatusIcon: 561 if (fShowLabel) 562 fShowStatusIcon = !fShowStatusIcon; 563 else 564 fShowStatusIcon = true; 565 566 Update(true); 567 break; 568 569 case kMsgToggleExtInfo: 570 _OpenExtendedWindow(); 571 break; 572 573 case B_ABOUT_REQUESTED: 574 _AboutRequested(); 575 break; 576 577 case B_QUIT_REQUESTED: 578 _Quit(); 579 break; 580 581 default: 582 PowerStatusView::MessageReceived(message); 583 } 584 } 585 586 587 void 588 PowerStatusReplicant::MouseDown(BPoint point) 589 { 590 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 591 menu->SetFont(be_plain_font); 592 593 BMenuItem* item; 594 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show text label"), 595 new BMessage(kMsgToggleLabel))); 596 if (fShowLabel) 597 item->SetMarked(true); 598 menu->AddItem(item = new BMenuItem(B_TRANSLATE("Show status icon"), 599 new BMessage(kMsgToggleStatusIcon))); 600 if (fShowStatusIcon) 601 item->SetMarked(true); 602 menu->AddItem(new BMenuItem(!fShowTime ? B_TRANSLATE("Show time") : 603 B_TRANSLATE("Show percent"), 604 new BMessage(kMsgToggleTime))); 605 606 menu->AddSeparatorItem(); 607 menu->AddItem(new BMenuItem(B_TRANSLATE("Battery info" B_UTF8_ELLIPSIS), 608 new BMessage(kMsgToggleExtInfo))); 609 610 menu->AddSeparatorItem(); 611 menu->AddItem(new BMenuItem(B_TRANSLATE("About" B_UTF8_ELLIPSIS), 612 new BMessage(B_ABOUT_REQUESTED))); 613 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 614 new BMessage(B_QUIT_REQUESTED))); 615 menu->SetTargetForItems(this); 616 617 ConvertToScreen(&point); 618 menu->Go(point, true, false, true); 619 } 620 621 622 void 623 PowerStatusReplicant::_AboutRequested() 624 { 625 BAboutWindow* window = new BAboutWindow( 626 B_TRANSLATE_SYSTEM_NAME("PowerStatus"), kSignature); 627 628 const char* authors[] = { 629 "Axel Dörfler", 630 "Alexander von Gluck", 631 "Clemens Zeidler", 632 NULL 633 }; 634 635 window->AddCopyright(2006, "Haiku, Inc."); 636 window->AddAuthors(authors); 637 638 window->Show(); 639 } 640 641 642 void 643 PowerStatusReplicant::_Init() 644 { 645 fDriverInterface = new ACPIDriverInterface; 646 if (fDriverInterface->Connect() != B_OK) { 647 delete fDriverInterface; 648 fDriverInterface = new APMDriverInterface; 649 if (fDriverInterface->Connect() != B_OK) { 650 fprintf(stderr, "No power interface found.\n"); 651 _Quit(); 652 } 653 } 654 655 fExtendedWindow = NULL; 656 fMessengerExist = false; 657 fExtWindowMessenger = NULL; 658 659 fDriverInterface->StartWatching(this); 660 } 661 662 663 void 664 PowerStatusReplicant::_Quit() 665 { 666 if (fInDeskbar) { 667 BDeskbar deskbar; 668 deskbar.RemoveItem(kDeskbarItemName); 669 } else 670 be_app->PostMessage(B_QUIT_REQUESTED); 671 } 672 673 674 status_t 675 PowerStatusReplicant::_GetSettings(BFile& file, int mode) 676 { 677 BPath path; 678 status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, &path, 679 (mode & O_ACCMODE) != O_RDONLY); 680 if (status != B_OK) 681 return status; 682 683 path.Append("PowerStatus settings"); 684 685 return file.SetTo(path.Path(), mode); 686 } 687 688 689 void 690 PowerStatusReplicant::_LoadSettings() 691 { 692 fShowLabel = false; 693 694 BFile file; 695 if (_GetSettings(file, B_READ_ONLY) != B_OK) 696 return; 697 698 BMessage settings; 699 if (settings.Unflatten(&file) < B_OK) 700 return; 701 702 FromMessage(&settings); 703 } 704 705 706 void 707 PowerStatusReplicant::_SaveSettings() 708 { 709 BFile file; 710 if (_GetSettings(file, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE) != B_OK) 711 return; 712 713 BMessage settings('pwst'); 714 ToMessage(&settings); 715 716 ssize_t size = 0; 717 settings.Flatten(&file, &size); 718 } 719 720 721 void 722 PowerStatusReplicant::_OpenExtendedWindow() 723 { 724 if (!fExtendedWindow) { 725 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 726 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 727 fExtendedWindow->Show(); 728 return; 729 } 730 731 BMessage msg(B_SET_PROPERTY); 732 msg.AddSpecifier("Hidden", int32(0)); 733 if (fExtWindowMessenger->SendMessage(&msg) == B_BAD_PORT_ID) { 734 fExtendedWindow = new ExtendedInfoWindow(fDriverInterface); 735 if (fMessengerExist) 736 delete fExtWindowMessenger; 737 fExtWindowMessenger = new BMessenger(NULL, fExtendedWindow); 738 fMessengerExist = true; 739 fExtendedWindow->Show(); 740 } else 741 fExtendedWindow->Activate(); 742 743 } 744 745 746 // #pragma mark - 747 748 749 extern "C" _EXPORT BView* 750 instantiate_deskbar_item(void) 751 { 752 return new PowerStatusReplicant(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true); 753 } 754