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