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