1 /* 2 * Copyright 2006, 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 */ 8 9 10 #include "PowerStatusView.h" 11 #include "PowerStatus.h" 12 13 #include <arch/x86/apm.h> 14 #include <generic_syscall.h> 15 #include <syscalls.h> 16 // temporary, as long as there is no real power state API 17 18 #include <Alert.h> 19 #include <Application.h> 20 #include <Deskbar.h> 21 #include <Dragger.h> 22 #include <Drivers.h> 23 #include <MenuItem.h> 24 #include <MessageRunner.h> 25 #include <PopUpMenu.h> 26 #include <TextView.h> 27 28 #include <stdio.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <unistd.h> 32 33 34 extern "C" _EXPORT BView *instantiate_deskbar_item(void); 35 36 37 const uint32 kMsgUpdate = 'updt'; 38 const uint32 kMsgToggleLabel = 'tglb'; 39 const uint32 kMsgToggleTime = 'tgtm'; 40 const uint32 kMsgToggleStatusIcon = 'tgsi'; 41 42 const uint32 kMinIconWidth = 16; 43 const uint32 kMinIconHeight = 16; 44 45 const bigtime_t kUpdateInterval = 2000000; 46 // every two seconds 47 48 49 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 50 // definitions for the APM driver available for BeOS 51 enum { 52 APM_CONTROL = B_DEVICE_OP_CODES_END + 1, 53 APM_DUMP_POWER_STATUS, 54 APM_BIOS_CALL, 55 APM_SET_SAFETY 56 }; 57 58 #define BIOS_APM_GET_POWER_STATUS 0x530a 59 #endif 60 61 62 PowerStatusView::PowerStatusView(BRect frame, int32 resizingMode, bool inDeskbar) 63 : BView(frame, kDeskbarItemName, resizingMode, 64 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), 65 fInDeskbar(inDeskbar) 66 { 67 _Init(); 68 69 if (!inDeskbar) { 70 // we were obviously added to a standard window - let's add a dragger 71 frame.OffsetTo(B_ORIGIN); 72 frame.top = frame.bottom - 7; 73 frame.left = frame.right - 7; 74 BDragger* dragger = new BDragger(frame, this, 75 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 76 AddChild(dragger); 77 } else 78 _Update(); 79 } 80 81 82 PowerStatusView::PowerStatusView(BMessage* archive) 83 : BView(archive) 84 { 85 _Init(); 86 87 bool value; 88 if (archive->FindBool("show label", &value) == B_OK) 89 fShowLabel = value; 90 if (archive->FindBool("show icon", &value) == B_OK) 91 fShowStatusIcon = value; 92 if (archive->FindBool("show time", &value) == B_OK) 93 fShowTime = value; 94 } 95 96 97 PowerStatusView::~PowerStatusView() 98 { 99 #ifndef HAIKU_TARGET_PLATFORM_HAIKU 100 close(fDevice); 101 #endif 102 } 103 104 105 void 106 PowerStatusView::_Init() 107 { 108 fShowLabel = true; 109 fShowTime = false; 110 fShowStatusIcon = true; 111 112 fMessageRunner = NULL; 113 fPercent = -1; 114 fOnline = true; 115 fTimeLeft = 0; 116 117 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 118 uint32 version = 0; 119 status_t status = _kern_generic_syscall(APM_SYSCALLS, B_SYSCALL_INFO, 120 &version, sizeof(version)); 121 if (status == B_OK) { 122 battery_info info; 123 status = _kern_generic_syscall(APM_SYSCALLS, APM_GET_BATTERY_INFO, &info, 124 sizeof(battery_info)); 125 } 126 127 if (status != B_OK) { 128 fprintf(stderr, "No power interface found.\n"); 129 _Quit(); 130 } 131 #else 132 fDevice = open("/dev/misc/apm", O_RDONLY); 133 if (fDevice < 0) { 134 fprintf(stderr, "No power interface found.\n"); 135 _Quit(); 136 } 137 #endif 138 } 139 140 141 void 142 PowerStatusView::_Quit() 143 { 144 if (fInDeskbar) { 145 BDeskbar deskbar; 146 deskbar.RemoveItem(kDeskbarItemName); 147 } else 148 be_app->PostMessage(B_QUIT_REQUESTED); 149 } 150 151 152 PowerStatusView * 153 PowerStatusView::Instantiate(BMessage* archive) 154 { 155 if (!validate_instantiation(archive, "PowerStatusView")) 156 return NULL; 157 158 return new PowerStatusView(archive); 159 } 160 161 162 status_t 163 PowerStatusView::Archive(BMessage* archive, bool deep) const 164 { 165 status_t status = BView::Archive(archive, deep); 166 if (status == B_OK) 167 status = archive->AddString("add_on", kSignature); 168 if (status == B_OK) 169 status = archive->AddString("class", "PowerStatusView"); 170 if (status == B_OK) 171 status = archive->AddBool("show label", fShowLabel); 172 if (status == B_OK) 173 status = archive->AddBool("show icon", fShowStatusIcon); 174 if (status == B_OK) 175 status = archive->AddBool("show time", fShowTime); 176 177 return status; 178 } 179 180 181 void 182 PowerStatusView::AttachedToWindow() 183 { 184 BView::AttachedToWindow(); 185 if (Parent()) 186 SetViewColor(Parent()->ViewColor()); 187 else 188 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 189 190 SetLowColor(ViewColor()); 191 192 BMessage update(kMsgUpdate); 193 fMessageRunner = new BMessageRunner(this, &update, kUpdateInterval); 194 195 _Update(); 196 } 197 198 199 void 200 PowerStatusView::DetachedFromWindow() 201 { 202 delete fMessageRunner; 203 } 204 205 206 void 207 PowerStatusView::MessageReceived(BMessage *message) 208 { 209 switch (message->what) { 210 case kMsgUpdate: 211 _Update(); 212 break; 213 214 case kMsgToggleLabel: 215 fShowLabel = !fShowLabel; 216 _Update(true); 217 break; 218 219 case kMsgToggleTime: 220 fShowTime = !fShowTime; 221 _Update(true); 222 break; 223 224 case kMsgToggleStatusIcon: 225 fShowStatusIcon = !fShowStatusIcon; 226 _Update(true); 227 break; 228 229 case B_ABOUT_REQUESTED: 230 _AboutRequested(); 231 break; 232 233 case B_QUIT_REQUESTED: 234 _Quit(); 235 break; 236 237 default: 238 BView::MessageReceived(message); 239 } 240 } 241 242 243 void 244 PowerStatusView::_DrawBattery(BRect rect) 245 { 246 float quarter = floorf((rect.Height() + 1) / 4); 247 rect.top += quarter; 248 rect.bottom -= quarter; 249 250 rect.InsetBy(2, 0); 251 252 float left = rect.left; 253 rect.left += rect.Width() / 11; 254 255 float gap = 1; 256 if (rect.Height() > 8) { 257 rect.InsetBy(1, 1); 258 SetPenSize(2); 259 gap = 2; 260 } 261 262 if (fOnline) 263 SetHighColor(92, 92, 92); 264 265 StrokeRect(rect); 266 267 SetPenSize(1); 268 FillRect(BRect(left, floorf(rect.top + rect.Height() / 4) + 1, 269 rect.left, floorf(rect.bottom - rect.Height() / 4))); 270 271 int32 percent = fPercent; 272 if (percent > 100 || percent < 0) 273 percent = 100; 274 275 if (percent > 0) { 276 if (percent < 16) 277 SetHighColor(180, 0, 0); 278 279 rect.InsetBy(gap + 1, gap + 1); 280 if (gap > 1) { 281 rect.right++; 282 rect.bottom++; 283 } 284 285 rect.right = rect.left + rect.Width() * min_c(percent, 100) / 100.0; 286 FillRect(rect); 287 } 288 289 SetHighColor(0, 0, 0); 290 } 291 292 293 void 294 PowerStatusView::Draw(BRect updateRect) 295 { 296 float aspect = Bounds().Width() / Bounds().Height(); 297 bool below = aspect <= 1.0f; 298 299 font_height fontHeight; 300 GetFontHeight(&fontHeight); 301 float baseLine = ceilf(fontHeight.ascent); 302 303 char text[64]; 304 _SetLabel(text, sizeof(text)); 305 306 float textHeight = ceilf(fontHeight.descent + fontHeight.ascent); 307 float textWidth = StringWidth(text); 308 bool showLabel = fShowLabel && text[0]; 309 310 BRect iconRect; 311 312 if (fShowStatusIcon) { 313 iconRect = Bounds(); 314 if (showLabel) { 315 if (below) 316 iconRect.bottom -= textHeight + 4; 317 else 318 iconRect.right -= textWidth + 4; 319 } 320 321 // make a square 322 iconRect.bottom = min_c(iconRect.bottom, iconRect.right); 323 iconRect.right = iconRect.bottom; 324 325 if (iconRect.Width() + 1 >= kMinIconWidth 326 && iconRect.Height() + 1 >= kMinIconHeight) { 327 // TODO: have real icons 328 //if (!fOnline) 329 _DrawBattery(iconRect); 330 //else 331 // FillRect(iconRect); 332 } else { 333 // there is not enough space for the icon 334 iconRect.Set(0, 0, -1, -1); 335 } 336 } 337 338 if (showLabel) { 339 BPoint point(0, baseLine); 340 341 if (iconRect.IsValid()) { 342 if (below) { 343 point.x = (iconRect.Width() - textWidth) / 2; 344 point.y += iconRect.Height() + 2; 345 } else { 346 point.x = iconRect.Width() + 2; 347 point.y += (iconRect.Height() - textHeight) / 2; 348 } 349 } else { 350 point.x = (Bounds().Width() - textWidth) / 2; 351 point.y += (Bounds().Height() - textHeight) / 2; 352 } 353 354 DrawString(text, point); 355 } 356 } 357 358 359 void 360 PowerStatusView::MouseDown(BPoint point) 361 { 362 BPopUpMenu *menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 363 menu->SetFont(be_plain_font); 364 365 BMenuItem* item; 366 menu->AddItem(item = new BMenuItem("Show Text Label", new BMessage(kMsgToggleLabel))); 367 if (fShowLabel) 368 item->SetMarked(true); 369 menu->AddItem(item = new BMenuItem("Show Status Icon", 370 new BMessage(kMsgToggleStatusIcon))); 371 if (fShowStatusIcon) 372 item->SetMarked(true); 373 menu->AddItem(new BMenuItem(!fShowTime ? "Show Time" : "Show Percent", 374 new BMessage(kMsgToggleTime))); 375 376 menu->AddSeparatorItem(); 377 menu->AddItem(new BMenuItem("About" B_UTF8_ELLIPSIS, new BMessage(B_ABOUT_REQUESTED))); 378 menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED))); 379 menu->SetTargetForItems(this); 380 381 ConvertToScreen(&point); 382 menu->Go(point, true, false, true); 383 } 384 385 386 void 387 PowerStatusView::_AboutRequested() 388 { 389 BAlert *alert = new BAlert("about", "PowerStatus\n" 390 "\twritten by Axel Dörfler\n" 391 "\tCopyright 2006, Haiku, Inc.\n", "Ok"); 392 BTextView *view = alert->TextView(); 393 BFont font; 394 395 view->SetStylable(true); 396 397 view->GetFont(&font); 398 font.SetSize(18); 399 font.SetFace(B_BOLD_FACE); 400 view->SetFontAndColor(0, 11, &font); 401 402 alert->Go(); 403 } 404 405 406 void 407 PowerStatusView::_SetLabel(char* buffer, size_t bufferLength) 408 { 409 if (bufferLength < 1) 410 return; 411 412 buffer[0] = '\0'; 413 414 if (!fShowLabel) 415 return; 416 417 const char* open = ""; 418 const char* close = ""; 419 if (fOnline) { 420 open = "("; 421 close = ")"; 422 } 423 424 if (!fShowTime && fPercent >= 0) 425 snprintf(buffer, bufferLength, "%s%ld%%%s", open, fPercent, close); 426 else if (fShowTime && fTimeLeft >= 0) { 427 snprintf(buffer, bufferLength, "%s%ld:%ld%s", 428 open, fTimeLeft / 3600, (fTimeLeft / 60) % 60, close); 429 } 430 } 431 432 433 void 434 PowerStatusView::_Update(bool force) 435 { 436 int32 previousPercent = fPercent; 437 bool previousTimeLeft = fTimeLeft; 438 bool wasOnline = fOnline; 439 440 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 441 // TODO: retrieve data from APM/ACPI kernel interface 442 battery_info info; 443 status_t status = _kern_generic_syscall(APM_SYSCALLS, APM_GET_BATTERY_INFO, &info, 444 sizeof(battery_info)); 445 if (status == B_OK) { 446 fPercent = info.percent; 447 fTimeLeft = info.time_left; 448 fOnline = info.online; 449 } 450 #else 451 if (fDevice < 0) 452 return; 453 454 uint16 regs[6] = {0, 0, 0, 0, 0, 0}; 455 regs[0] = BIOS_APM_GET_POWER_STATUS; 456 regs[1] = 0x1; 457 if (ioctl(fDevice, APM_BIOS_CALL, regs) == 0) { 458 fOnline = (regs[1] >> 8) != 0 && (regs[1] >> 8) != 2; 459 fPercent = regs[2] & 255; 460 if (fPercent > 100) 461 fPercent = -1; 462 fTimeLeft = fPercent >= 0 ? regs[3] : -1; 463 if (fTimeLeft > 0xffff) 464 fTimeLeft = -1; 465 else if (fTimeLeft & 0x8000) 466 fTimeLeft = (fTimeLeft & 0x7fff) * 60; 467 } 468 #endif 469 470 if (fInDeskbar) { 471 // make sure the tray icon is large enough 472 float width = fShowStatusIcon ? kMinIconWidth + 2 : 0; 473 474 if (fShowLabel) { 475 char text[64]; 476 _SetLabel(text, sizeof(text)); 477 478 if (text[0]) 479 width += ceilf(StringWidth(text)) + 4; 480 } 481 if (width == 0) { 482 // make sure we're not going away completely 483 width = 8; 484 } 485 486 if (width != Bounds().Width()) 487 ResizeTo(width, Bounds().Height()); 488 } 489 490 if (force || wasOnline != fOnline 491 || (fShowTime && fTimeLeft != previousTimeLeft) 492 || (!fShowTime && fPercent != previousPercent)) 493 Invalidate(); 494 } 495 496 497 // #pragma mark - 498 499 500 extern "C" _EXPORT BView * 501 instantiate_deskbar_item(void) 502 { 503 return new PowerStatusView(BRect(0, 0, 15, 15), B_FOLLOW_NONE, true); 504 } 505 506