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