1 /* 2 * Copyright (c) 2005-2009, Haiku, Inc. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * DarkWyrm <bpmagic@columbus.rr.com> 7 * René Gollent 8 */ 9 10 #include <ctype.h> 11 #include <stdio.h> 12 #include <sys/utsname.h> 13 #include <time.h> 14 #include <unistd.h> 15 16 #include <map> 17 #include <string> 18 19 #include <AppFileInfo.h> 20 #include <Application.h> 21 #include <Bitmap.h> 22 #include <File.h> 23 #include <FindDirectory.h> 24 #include <Font.h> 25 #include <fs_attr.h> 26 #include <LayoutBuilder.h> 27 #include <MessageRunner.h> 28 #include <Messenger.h> 29 #include <OS.h> 30 #include <Path.h> 31 #include <Resources.h> 32 #include <Screen.h> 33 #include <ScrollView.h> 34 #include <String.h> 35 #include <StringView.h> 36 #include <TranslationUtils.h> 37 #include <TranslatorFormats.h> 38 #include <View.h> 39 #include <Volume.h> 40 #include <VolumeRoster.h> 41 #include <Window.h> 42 43 #include <AppMisc.h> 44 #include <AutoDeleter.h> 45 #include <cpu_type.h> 46 47 #include "HyperTextActions.h" 48 #include "HyperTextView.h" 49 #include "Utilities.h" 50 51 52 #ifndef LINE_MAX 53 #define LINE_MAX 2048 54 #endif 55 56 #define SCROLL_CREDITS_VIEW 'mviv' 57 58 59 static const char *UptimeToString(char string[], size_t size); 60 static const char *MemUsageToString(char string[], size_t size, 61 system_info *info); 62 63 static const rgb_color kDarkGrey = { 100, 100, 100, 255 }; 64 static const rgb_color kHaikuGreen = { 42, 131, 36, 255 }; 65 static const rgb_color kHaikuOrange = { 255, 69, 0, 255 }; 66 static const rgb_color kHaikuYellow = { 255, 176, 0, 255 }; 67 static const rgb_color kLinkBlue = { 80, 80, 200, 255 }; 68 69 70 class AboutApp : public BApplication { 71 public: 72 AboutApp(); 73 }; 74 75 class AboutWindow : public BWindow { 76 public: 77 AboutWindow(); 78 79 virtual bool QuitRequested(); 80 }; 81 82 class LogoView : public BView { 83 public: 84 LogoView(); 85 virtual ~LogoView(); 86 87 virtual BSize MinSize(); 88 virtual BSize MaxSize(); 89 90 virtual void Draw(BRect updateRect); 91 92 private: 93 BBitmap* fLogo; 94 }; 95 96 class CropView : public BView { 97 public: 98 CropView(BView* target, int32 left, int32 top, 99 int32 right, int32 bottom); 100 virtual ~CropView(); 101 102 virtual BSize MinSize(); 103 virtual BSize MaxSize(); 104 105 virtual void DoLayout(); 106 107 private: 108 BView* fTarget; 109 int32 fCropLeft; 110 int32 fCropTop; 111 int32 fCropRight; 112 int32 fCropBottom; 113 }; 114 115 class AboutView : public BView { 116 public: 117 AboutView(); 118 ~AboutView(); 119 120 virtual void AttachedToWindow(); 121 virtual void Pulse(); 122 123 virtual void MessageReceived(BMessage* msg); 124 virtual void MouseDown(BPoint point); 125 126 void AddCopyrightEntry(const char* name, 127 const char* text, 128 const StringVector& licenses, 129 const char* url); 130 void AddCopyrightEntry(const char* name, 131 const char* text, const char* url = NULL); 132 void PickRandomHaiku(); 133 134 135 private: 136 typedef std::map<std::string, PackageCredit*> PackageCreditMap; 137 138 private: 139 BView* _CreateLabel(const char* name, const char* label); 140 BView* _CreateCreditsView(); 141 status_t _GetLicensePath(const char* license, 142 BPath& path); 143 void _AddCopyrightsFromAttribute(); 144 void _AddPackageCredit(const PackageCredit& package); 145 void _AddPackageCreditEntries(); 146 147 BStringView* fMemView; 148 BTextView* fUptimeView; 149 BView* fInfoView; 150 HyperTextView* fCreditsView; 151 152 BBitmap* fLogo; 153 154 bigtime_t fLastActionTime; 155 BMessageRunner* fScrollRunner; 156 PackageCreditMap fPackageCredits; 157 }; 158 159 160 // #pragma mark - 161 162 163 AboutApp::AboutApp() 164 : BApplication("application/x-vnd.Haiku-About") 165 { 166 AboutWindow* window = new AboutWindow(); 167 window->Show(); 168 } 169 170 171 // #pragma mark - 172 173 174 AboutWindow::AboutWindow() 175 : BWindow(BRect(0, 0, 500, 300), "About This System", B_TITLED_WINDOW, 176 B_AUTO_UPDATE_SIZE_LIMITS | B_NOT_ZOOMABLE) 177 { 178 SetLayout(new BGroupLayout(B_VERTICAL)); 179 AddChild(new AboutView()); 180 181 // Make sure we take the minimal window size into account when centering 182 BSize size = GetLayout()->MinSize(); 183 ResizeTo(max_c(size.width, Bounds().Width()), 184 max_c(size.height, Bounds().Height())); 185 186 MoveTo((BScreen().Frame().Width() - Bounds().Width()) / 2, 187 (BScreen().Frame().Height() - Bounds().Height()) / 2 ); 188 } 189 190 191 bool 192 AboutWindow::QuitRequested() 193 { 194 be_app->PostMessage(B_QUIT_REQUESTED); 195 return true; 196 } 197 198 199 // #pragma mark - LogoView 200 201 202 LogoView::LogoView() 203 : BView("logo", B_WILL_DRAW) 204 { 205 fLogo = BTranslationUtils::GetBitmap(B_PNG_FORMAT, "haikulogo.png"); 206 SetViewColor(255, 255, 255); 207 } 208 209 210 LogoView::~LogoView() 211 { 212 delete fLogo; 213 } 214 215 216 BSize 217 LogoView::MinSize() 218 { 219 if (fLogo == NULL) 220 return BSize(0, 0); 221 222 return BSize(fLogo->Bounds().Width(), fLogo->Bounds().Height()); 223 } 224 225 226 BSize 227 LogoView::MaxSize() 228 { 229 if (fLogo == NULL) 230 return BSize(0, 0); 231 232 return BSize(B_SIZE_UNLIMITED, fLogo->Bounds().Height()); 233 } 234 235 236 void 237 LogoView::Draw(BRect updateRect) 238 { 239 if (fLogo != NULL) { 240 DrawBitmap(fLogo, 241 BPoint((Bounds().Width() - fLogo->Bounds().Width()) / 2, 0)); 242 } 243 } 244 245 246 // #pragma mark - CropView 247 248 249 CropView::CropView(BView* target, int32 left, int32 top, int32 right, 250 int32 bottom) 251 : BView("crop view", 0), 252 fTarget(target), 253 fCropLeft(left), 254 fCropTop(top), 255 fCropRight(right), 256 fCropBottom(bottom) 257 { 258 AddChild(target); 259 } 260 261 262 CropView::~CropView() 263 { 264 } 265 266 267 BSize 268 CropView::MinSize() 269 { 270 if (fTarget == NULL) 271 return BSize(); 272 273 BSize size = fTarget->MinSize(); 274 if (size.width != B_SIZE_UNSET) 275 size.width -= fCropLeft + fCropRight; 276 if (size.height != B_SIZE_UNSET) 277 size.height -= fCropTop + fCropBottom; 278 279 return size; 280 } 281 282 283 BSize 284 CropView::MaxSize() 285 { 286 if (fTarget == NULL) 287 return BSize(); 288 289 BSize size = fTarget->MaxSize(); 290 if (size.width != B_SIZE_UNSET) 291 size.width -= fCropLeft + fCropRight; 292 if (size.height != B_SIZE_UNSET) 293 size.height -= fCropTop + fCropBottom; 294 295 return size; 296 } 297 298 299 void 300 CropView::DoLayout() 301 { 302 BView::DoLayout(); 303 304 if (fTarget == NULL) 305 return; 306 307 fTarget->MoveTo(-fCropLeft, -fCropTop); 308 fTarget->ResizeTo(Bounds().Width() + fCropLeft + fCropRight, 309 Bounds().Height() + fCropTop + fCropBottom); 310 } 311 312 313 // #pragma mark - AboutView 314 315 316 AboutView::AboutView() 317 : BView("aboutview", B_WILL_DRAW | B_PULSE_NEEDED), 318 fLastActionTime(system_time()), 319 fScrollRunner(NULL) 320 { 321 // Begin Construction of System Information controls 322 323 system_info systemInfo; 324 get_system_info(&systemInfo); 325 326 // Create all the various labels for system infomation 327 328 // OS Version 329 330 char string[1024]; 331 strcpy(string, "Unknown"); 332 333 // the version is stored in the BEOS:APP_VERSION attribute of libbe.so 334 BPath path; 335 if (find_directory(B_BEOS_LIB_DIRECTORY, &path) == B_OK) { 336 path.Append("libbe.so"); 337 338 BAppFileInfo appFileInfo; 339 version_info versionInfo; 340 BFile file; 341 if (file.SetTo(path.Path(), B_READ_ONLY) == B_OK 342 && appFileInfo.SetTo(&file) == B_OK 343 && appFileInfo.GetVersionInfo(&versionInfo, 344 B_APP_VERSION_KIND) == B_OK 345 && versionInfo.short_info[0] != '\0') 346 strcpy(string, versionInfo.short_info); 347 } 348 349 // Add revision from uname() info 350 utsname unameInfo; 351 if (uname(&unameInfo) == 0) { 352 long revision; 353 if (sscanf(unameInfo.version, "r%ld", &revision) == 1) { 354 char version[16]; 355 snprintf(version, sizeof(version), "%ld", revision); 356 strlcat(string, " (Revision ", sizeof(string)); 357 strlcat(string, version, sizeof(string)); 358 strlcat(string, ")", sizeof(string)); 359 } 360 } 361 362 BStringView* versionView = new BStringView("ostext", string); 363 versionView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 364 B_ALIGN_VERTICAL_UNSET)); 365 366 // GCC version 367 #if __GNUC__ != 2 368 snprintf(string, sizeof(string), "GCC %d", __GNUC__); 369 370 BStringView* gccView = new BStringView("gcctext", string); 371 gccView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 372 B_ALIGN_VERTICAL_UNSET)); 373 #endif 374 375 // CPU count, type and clock speed 376 char processorLabel[256]; 377 if (systemInfo.cpu_count > 1) { 378 snprintf(processorLabel, sizeof(processorLabel), "%ld Processors:", 379 systemInfo.cpu_count); 380 } else 381 strlcpy(processorLabel, "Processor:", sizeof(processorLabel)); 382 383 BString cpuType; 384 cpuType << get_cpu_vendor_string(systemInfo.cpu_type) 385 << " " << get_cpu_model_string(&systemInfo); 386 387 BStringView* cpuView = new BStringView("cputext", cpuType.String()); 388 cpuView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 389 B_ALIGN_VERTICAL_UNSET)); 390 391 int32 clockSpeed = get_rounded_cpu_speed(); 392 if (clockSpeed < 1000) 393 sprintf(string,"%ld MHz", clockSpeed); 394 else 395 sprintf(string,"%.2f GHz", clockSpeed / 1000.0f); 396 397 BStringView* frequencyView = new BStringView("frequencytext", string); 398 frequencyView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 399 B_ALIGN_VERTICAL_UNSET)); 400 401 // RAM 402 fMemView = new BStringView("ramtext", 403 MemUsageToString(string, sizeof(string), &systemInfo)); 404 fMemView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 405 B_ALIGN_VERTICAL_UNSET)); 406 407 // Kernel build time/date 408 snprintf(string, sizeof(string), "%s %s", 409 systemInfo.kernel_build_date, systemInfo.kernel_build_time); 410 411 BStringView* kernelView = new BStringView("kerneltext", string); 412 kernelView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 413 B_ALIGN_VERTICAL_UNSET)); 414 415 // Uptime 416 fUptimeView = new BTextView("uptimetext"); 417 fUptimeView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 418 fUptimeView->MakeEditable(false); 419 fUptimeView->MakeSelectable(false); 420 fUptimeView->SetWordWrap(true); 421 422 fUptimeView->SetText(UptimeToString(string, sizeof(string))); 423 424 const float offset = 5; 425 426 SetLayout(new BGroupLayout(B_HORIZONTAL)); 427 428 BLayoutBuilder::Group<>((BGroupLayout*)GetLayout()) 429 .AddGroup(B_VERTICAL) 430 .Add(new LogoView()) 431 .AddGroup(B_VERTICAL) 432 .Add(_CreateLabel("oslabel", "Version:")) 433 .Add(versionView) 434 #if __GNUC__ != 2 435 .Add(gccView) 436 #endif 437 .AddStrut(offset) 438 .Add(_CreateLabel("cpulabel", processorLabel)) 439 .Add(cpuView) 440 .Add(frequencyView) 441 .AddStrut(offset) 442 .Add(_CreateLabel("memlabel", "Memory:")) 443 .Add(fMemView) 444 .AddStrut(offset) 445 .Add(_CreateLabel("kernellabel", "Kernel:")) 446 .Add(kernelView) 447 .AddStrut(offset) 448 .Add(_CreateLabel("uptimelabel", "Time Running:")) 449 .Add(fUptimeView) 450 .SetInsets(5, 5, 5, 5) 451 .End() 452 // TODO: investigate: adding this causes the time to be cut 453 //.AddGlue() 454 .End() 455 .Add(_CreateCreditsView()); 456 457 float min = fMemView->MinSize().width * 1.1f; 458 fCreditsView->SetExplicitMinSize(BSize(min, min)); 459 } 460 461 462 AboutView::~AboutView() 463 { 464 delete fScrollRunner; 465 } 466 467 468 void 469 AboutView::AttachedToWindow() 470 { 471 BView::AttachedToWindow(); 472 Window()->SetPulseRate(500000); 473 SetEventMask(B_POINTER_EVENTS); 474 } 475 476 477 void 478 AboutView::MouseDown(BPoint point) 479 { 480 BRect r(92, 26, 105, 31); 481 if (r.Contains(point)) { 482 printf("Easter Egg\n"); 483 PickRandomHaiku(); 484 } 485 486 if (Bounds().Contains(point)) { 487 fLastActionTime = system_time(); 488 delete fScrollRunner; 489 fScrollRunner = NULL; 490 } 491 } 492 493 494 void 495 AboutView::Pulse() 496 { 497 char string[255]; 498 system_info info; 499 get_system_info(&info); 500 fUptimeView->SetText(UptimeToString(string, sizeof(string))); 501 fMemView->SetText(MemUsageToString(string, sizeof(string), &info)); 502 503 if (fScrollRunner == NULL && system_time() > fLastActionTime + 10000000) { 504 BMessage message(SCROLL_CREDITS_VIEW); 505 //fScrollRunner = new BMessageRunner(this, &message, 25000, -1); 506 } 507 } 508 509 510 void 511 AboutView::MessageReceived(BMessage *msg) 512 { 513 switch (msg->what) { 514 case SCROLL_CREDITS_VIEW: 515 { 516 BScrollBar *scrollBar = fCreditsView->ScrollBar(B_VERTICAL); 517 if (scrollBar == NULL) 518 break; 519 float max, min; 520 scrollBar->GetRange(&min, &max); 521 if (scrollBar->Value() < max) 522 fCreditsView->ScrollBy(0, 1); 523 524 break; 525 } 526 527 default: 528 BView::MessageReceived(msg); 529 break; 530 } 531 } 532 533 534 void 535 AboutView::AddCopyrightEntry(const char *name, const char *text, 536 const char *url) 537 { 538 AddCopyrightEntry(name, text, StringVector(), url); 539 } 540 541 542 void 543 AboutView::AddCopyrightEntry(const char *name, const char *text, 544 const StringVector& licenses, const char *url) 545 { 546 BFont font(be_bold_font); 547 //font.SetSize(be_bold_font->Size()); 548 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 549 550 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuYellow); 551 fCreditsView->Insert(name); 552 fCreditsView->Insert("\n"); 553 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 554 fCreditsView->Insert(text); 555 fCreditsView->Insert("\n"); 556 557 if (licenses.CountStrings() > 0) { 558 if (licenses.CountStrings() > 1) 559 fCreditsView->Insert("Licenses: "); 560 else 561 fCreditsView->Insert("License: "); 562 563 for (int32 i = 0; i < licenses.CountStrings(); i++) { 564 const char* license = licenses.StringAt(i); 565 566 if (i > 0) 567 fCreditsView->Insert(", "); 568 569 BPath licensePath; 570 if (_GetLicensePath(license, licensePath) == B_OK) { 571 fCreditsView->InsertHyperText(license, 572 new OpenFileAction(licensePath.Path())); 573 } else 574 fCreditsView->Insert(license); 575 } 576 577 fCreditsView->Insert("\n"); 578 } 579 580 if (url) { 581 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kLinkBlue); 582 fCreditsView->InsertHyperText(url, new URLAction(url)); 583 fCreditsView->Insert("\n"); 584 } 585 fCreditsView->Insert("\n"); 586 } 587 588 589 void 590 AboutView::PickRandomHaiku() 591 { 592 BFile fortunes( 593 #ifdef __HAIKU__ 594 "/etc/fortunes/Haiku", 595 #else 596 "data/etc/fortunes/Haiku", 597 #endif 598 B_READ_ONLY); 599 struct stat st; 600 if (fortunes.InitCheck() < B_OK) 601 return; 602 if (fortunes.GetStat(&st) < B_OK) 603 return; 604 char *buff = (char *)malloc((size_t)st.st_size + 1); 605 if (!buff) 606 return; 607 buff[(size_t)st.st_size] = '\0'; 608 BList haikuList; 609 if (fortunes.Read(buff, (size_t)st.st_size) == (ssize_t)st.st_size) { 610 char *p = buff; 611 while (p && *p) { 612 char *e = strchr(p, '%'); 613 BString *s = new BString(p, e ? (e - p) : -1); 614 haikuList.AddItem(s); 615 p = e; 616 if (p && (*p == '%')) 617 p++; 618 if (p && (*p == '\n')) 619 p++; 620 } 621 } 622 free(buff); 623 if (haikuList.CountItems() < 1) 624 return; 625 BString *s = (BString *)haikuList.ItemAt(rand() % haikuList.CountItems()); 626 BFont font(be_bold_font); 627 font.SetSize(be_bold_font->Size()); 628 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 629 fCreditsView->SelectAll(); 630 fCreditsView->Delete(); 631 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kDarkGrey); 632 fCreditsView->Insert(s->String()); 633 fCreditsView->Insert("\n"); 634 while ((s = (BString *)haikuList.RemoveItem((int32)0))) { 635 delete s; 636 } 637 } 638 639 640 BView* 641 AboutView::_CreateLabel(const char* name, const char* label) 642 { 643 BStringView* labelView = new BStringView(name, label); 644 labelView->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, 645 B_ALIGN_VERTICAL_UNSET)); 646 labelView->SetFont(be_bold_font); 647 return labelView; 648 } 649 650 651 BView* 652 AboutView::_CreateCreditsView() 653 { 654 // Begin construction of the credits view 655 fCreditsView = new HyperTextView("credits"); 656 fCreditsView->SetFlags(fCreditsView->Flags() | B_FRAME_EVENTS); 657 fCreditsView->SetStylable(true); 658 fCreditsView->MakeEditable(false); 659 fCreditsView->SetWordWrap(true); 660 fCreditsView->SetInsets(5, 5, 5, 5); 661 662 BScrollView* creditsScroller = new BScrollView("creditsScroller", 663 fCreditsView, B_WILL_DRAW | B_FRAME_EVENTS, false, true, 664 B_PLAIN_BORDER); 665 666 // Haiku copyright 667 BFont font(be_bold_font); 668 font.SetSize(font.Size() + 4); 669 670 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuGreen); 671 fCreditsView->Insert("Haiku\n"); 672 673 char string[1024]; 674 time_t time = ::time(NULL); 675 struct tm* tm = localtime(&time); 676 int32 year = tm->tm_year + 1900; 677 if (year < 2008) 678 year = 2008; 679 snprintf(string, sizeof(string), 680 COPYRIGHT_STRING "2001-%ld The Haiku project. ", year); 681 682 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 683 fCreditsView->Insert(string); 684 685 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 686 fCreditsView->Insert("The copyright to the Haiku code is property of " 687 "Haiku, Inc. or of the respective authors where expressly noted " 688 "in the source." 689 "\n\n"); 690 691 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kLinkBlue); 692 fCreditsView->InsertHyperText("http://www.haiku-os.org", 693 new URLAction("http://www.haiku-os.org")); 694 fCreditsView->Insert("\n\n"); 695 696 font.SetSize(be_bold_font->Size()); 697 font.SetFace(B_BOLD_FACE | B_ITALIC_FACE); 698 699 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 700 fCreditsView->Insert("Current Maintainers:\n"); 701 702 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 703 fCreditsView->Insert( 704 "Ithamar R. Adema\n" 705 "Bruno G. Albuquerque\n" 706 "Stephan Aßmus\n" 707 "Salvatore Benedetto\n" 708 "Stefano Ceccherini\n" 709 "Rudolf Cornelissen\n" 710 "Alexandre Deckner\n" 711 "Adrien Destugues\n" 712 "Oliver Ruiz Dorantes\n" 713 "Axel Dörfler\n" 714 "Jérôme Duval\n" 715 "René Gollent\n" 716 "Bryce Groff\n" 717 "Karsten Heimrich\n" 718 "Philippe Houdoin\n" 719 "Maurice Kalinowski\n" 720 "Euan Kirkhope\n" 721 "Ryan Leavengood\n" 722 "Michael Lotz\n" 723 "David McPaul\n" 724 "Fredrik Modéen\n" 725 "Marcus Overhagen\n" 726 "Michael Pfeiffer\n" 727 "François Revol\n" 728 "Philippe Saint-Pierre\n" 729 "Andrej Spielmann\n" 730 "Jonas Sundström\n" 731 "Oliver Tappe\n" 732 "Gerasim Troeglazov\n" 733 "Ingo Weinhold\n" 734 "Artur Wyszynski\n" 735 "Clemens Zeidler\n" 736 "Siarzhuk Zharski\n" 737 "\n"); 738 739 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 740 fCreditsView->Insert("Past Maintainers:\n"); 741 742 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 743 fCreditsView->Insert( 744 "Andrew Bachmann\n" 745 "Tyler Dauwalder\n" 746 "Daniel Furrer\n" 747 "Andre Alves Garzia\n" 748 "Erik Jaesler\n" 749 "Marcin Konicki\n" 750 "Waldemar Kornewald\n" 751 "Thomas Kurschel\n" 752 "Frans Van Nispen\n" 753 "Adi Oanca\n" 754 "Michael Phipps\n" 755 "Niels Sascha Reedijk\n" 756 "David Reid\n" 757 "Hugo Santos\n" 758 "Alexander G. M. Smith\n" 759 "Bryan Varner\n" 760 "Nathan Whitehorn\n" 761 "Michael Wilber\n" 762 "Jonathan Yoder\n" 763 "Gabe Yoder\n" 764 "\n"); 765 766 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 767 fCreditsView->Insert("Website, Marketing & Documentation:\n"); 768 769 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 770 fCreditsView->Insert( 771 "Phil Greenway\n" 772 "Gavin James\n" 773 "Matt Madia\n" 774 "Jorge G. Mare (aka Koki)\n" 775 "Urias McCullough\n" 776 "Niels Sascha Reedijk\n" 777 "Joachim Seemer (Humdinger)\n" 778 "Jonathan Yoder\n" 779 "\n"); 780 781 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 782 fCreditsView->Insert("Contributors:\n"); 783 784 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 785 fCreditsView->Insert( 786 "Andrea Anzani\n" 787 "Andre Braga\n" 788 "Bruce Cameron\n" 789 "Greg Crain\n" 790 "David Dengg\n" 791 "John Drinkwater\n" 792 "Cian Duffy\n" 793 "Vincent Duvert\n" 794 "Fredrik Ekdahl\n" 795 "Mark Erben\n" 796 "Christian Fasshauer\n" 797 "Andreas Färber\n" 798 "Marc Flerackers\n" 799 "Michele Frau (zuMi)\n" 800 "Matthijs Hollemans\n" 801 "Mathew Hounsell\n" 802 "Morgan Howe\n" 803 "Ma Jie\n" 804 "Carwyn Jones\n" 805 "Vasilis Kaoutsis\n" 806 "James Kim\n" 807 "Shintaro Kinugawa\n" 808 "Jan Klötzke\n" 809 "Kurtis Kopf\n" 810 "Tomáš Kučera\n" 811 "Luboš Kulič\n" 812 "Elad Lahav\n" 813 "Anthony Lee\n" 814 "Santiago Lema\n" 815 "Raynald Lesieur\n" 816 "Oscar Lesta\n" 817 "Jerome Leveque\n" 818 "Christof Lutteroth\n" 819 "Graham MacDonald\n" 820 "Brecht Machiels\n" 821 "Jan Matějek\n" 822 "Brian Matzon\n" 823 "Christopher ML Zumwalt May\n" 824 "Andrew McCall\n" 825 "Scott McCreary\n" 826 "Marius Middelthon\n" 827 "Marco Minutoli\n" 828 "Misza\n" 829 "MrSiggler\n" 830 "Alan Murta\n" 831 "Raghuram Nagireddy\n" 832 "Jeroen Oortwijn (idefix)\n" 833 "Pahtz\n" 834 "Michael Paine\n" 835 "Adrian Panasiuk\n" 836 "Francesco Piccinno\n" 837 "David Powell\n" 838 "Jeremy Rand\n" 839 "Hartmut Reh\n" 840 "Daniel Reinhold\n" 841 "Chris Roberts\n" 842 "Samuel Rodriguez Perez\n" 843 "Thomas Roell\n" 844 "Rafael Romo\n" 845 "Ralf Schülke\n" 846 "Reznikov Sergei\n" 847 "Zousar Shaker\n" 848 "Daniel Switkin\n" 849 "Atsushi Takamatsu\n" 850 "James Urquhart\n" 851 "Jason Vandermark\n" 852 "Sandor Vroemisse\n" 853 "Denis Washington\n" 854 "Ulrich Wimboeck\n" 855 "Johannes Wischert\n" 856 "James Woodcock\n" 857 "Gerald Zajac\n" 858 "Łukasz Zemczak\n" 859 "JiSheng Zhang\n" 860 "Zhao Shuai\n" 861 "\n" B_UTF8_ELLIPSIS " and probably some more we forgot to mention " 862 "(sorry!)" 863 "\n\n"); 864 865 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 866 fCreditsView->Insert("Special Thanks To:\n"); 867 868 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 869 fCreditsView->Insert("Travis Geiselbrecht (and his NewOS kernel)\n"); 870 fCreditsView->Insert("Michael Phipps (project founder)\n\n"); 871 fCreditsView->Insert("The Haiku-Ports Team\n"); 872 fCreditsView->Insert("The Haikuware Team and their Bounty Program\n"); 873 fCreditsView->Insert("The BeGeistert Team\n\n"); 874 fCreditsView->Insert("... and the many community members making " 875 "donations!\n\n"); 876 877 // copyrights for various projects we use 878 879 BPath mitPath; 880 _GetLicensePath("MIT", mitPath); 881 882 font.SetSize(be_bold_font->Size() + 4); 883 font.SetFace(B_BOLD_FACE); 884 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuGreen); 885 fCreditsView->Insert("\nCopyrights\n\n"); 886 887 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 888 fCreditsView->Insert("[Click a license name to read the respective " 889 "license.]\n\n"); 890 891 // Haiku license 892 fCreditsView->Insert("The code that is unique to Haiku, especially the " 893 "kernel and all code that applications may link against, is " 894 "distributed under the terms of the "); 895 fCreditsView->InsertHyperText("MIT license", 896 new OpenFileAction(mitPath.Path())); 897 fCreditsView->Insert(". Some system libraries contain third party code " 898 "distributed under the LGPL license. You can find the copyrights " 899 "to third party code below.\n\n"); 900 901 // GNU copyrights 902 AddCopyrightEntry("The GNU Project", 903 "Contains software from the GNU Project, " 904 "released under the GPL and LGPL licences:\n" 905 "GNU C Library, " 906 "GNU coretools, diffutils, findutils, " 907 "sharutils, gawk, bison, m4, make, " 908 "gdb, wget, ncurses, termcap, " 909 "Bourne Again Shell.\n" 910 COPYRIGHT_STRING "The Free Software Foundation.", 911 StringVector("GNU LGPL v2.1", "GNU GPL v2", "GNU GPL v3", NULL), 912 "http://www.gnu.org"); 913 914 // FreeBSD copyrights 915 AddCopyrightEntry("The FreeBSD Project", 916 "Contains software from the FreeBSD Project, " 917 "released under the BSD licence:\n" 918 "cal, ftpd, ping, telnet, " 919 "telnetd, traceroute\n" 920 COPYRIGHT_STRING "1994-2008 The FreeBSD Project. " 921 "All rights reserved.", 922 "http://www.freebsd.org"); 923 // TODO: License! 924 925 // NetBSD copyrights 926 AddCopyrightEntry("The NetBSD Project", 927 "Contains software developed by the NetBSD, " 928 "Foundation, Inc. and its contributors:\n" 929 "ftp, tput\n" 930 COPYRIGHT_STRING "1996-2008 The NetBSD Foundation, Inc. " 931 "All rights reserved.", 932 "http://www.netbsd.org"); 933 // TODO: License! 934 935 // FFMpeg copyrights 936 _AddPackageCredit(PackageCredit("FFMpeg libavcodec") 937 .SetCopyright(COPYRIGHT_STRING "2000-2007 Fabrice Bellard, et al.") 938 .SetURL("http://www.ffmpeg.org")); 939 // TODO: License! 940 941 // AGG copyrights 942 _AddPackageCredit(PackageCredit("AntiGrain Geometry") 943 .SetCopyright(COPYRIGHT_STRING "2002-2006 Maxim Shemanarev (McSeem).") 944 .SetURL("http://www.antigrain.com")); 945 // TODO: License! 946 947 // PDFLib copyrights 948 _AddPackageCredit(PackageCredit("PDFLib") 949 .SetCopyright(COPYRIGHT_STRING "1997-2006 PDFlib GmbH and Thomas Merz. " 950 "All rights reserved.\n" 951 "PDFlib and PDFlib logo are registered trademarks of PDFlib GmbH.") 952 .SetURL("http://www.pdflib.com")); 953 // TODO: License! 954 955 // FreeType copyrights 956 _AddPackageCredit(PackageCredit("FreeType2") 957 .SetCopyright("Portions of this software are copyright " 958 B_UTF8_COPYRIGHT " 1996-2006 " 959 "The FreeType Project. All rights reserved.") 960 .SetURL("http://www.freetype.org")); 961 // TODO: License! 962 963 // Mesa3D (http://www.mesa3d.org) copyrights 964 _AddPackageCredit(PackageCredit("Mesa") 965 .SetCopyright(COPYRIGHT_STRING "1999-2006 Brian Paul. " 966 "Mesa3D project. All rights reserved.") 967 .SetURL("http://www.mesa3d.org")); 968 // TODO: License! 969 970 // SGI's GLU implementation copyrights 971 _AddPackageCredit(PackageCredit("GLU") 972 .SetCopyright(COPYRIGHT_STRING 973 "1991-2000 Silicon Graphics, Inc. " 974 "SGI's Software FreeB license. All rights reserved.")); 975 // TODO: License! 976 977 // GLUT implementation copyrights 978 _AddPackageCredit(PackageCredit("GLUT") 979 .SetCopyrights(COPYRIGHT_STRING "1994-1997 Mark Kilgard. " 980 "All rights reserved.", 981 COPYRIGHT_STRING "1997 Be Inc.", 982 COPYRIGHT_STRING "1999 Jake Hamby.", 983 NULL)); 984 // TODO: License! 985 986 // OpenGroup & DEC (BRegion backend) copyright 987 _AddPackageCredit(PackageCredit("BRegion backend (XFree86)") 988 .SetCopyrights(COPYRIGHT_STRING "1987, 1988, 1998 The Open Group.", 989 COPYRIGHT_STRING "1987, 1988 Digital Equipment " 990 "Corporation, Maynard, Massachusetts.\n" 991 "All rights reserved.", 992 NULL)); 993 // TODO: License! 994 995 // Konatu font 996 _AddPackageCredit(PackageCredit("Konatu font") 997 .SetCopyright(COPYRIGHT_STRING "2002- MASUDA mitiya.\n" 998 "MIT license. All rights reserved.")); 999 // TODO: License! 1000 1001 // expat copyrights 1002 _AddPackageCredit(PackageCredit("expat") 1003 .SetCopyrights(COPYRIGHT_STRING 1004 "1998, 1999, 2000 Thai Open Source " 1005 "Software Center Ltd and Clark Cooper.", 1006 COPYRIGHT_STRING "2001, 2002, 2003 Expat maintainers.", 1007 NULL)); 1008 // TODO: License! 1009 1010 // zlib copyrights 1011 _AddPackageCredit(PackageCredit("zlib") 1012 .SetCopyright(COPYRIGHT_STRING 1013 "1995-2004 Jean-loup Gailly and Mark Adler.")); 1014 // TODO: License! 1015 1016 // zip copyrights 1017 _AddPackageCredit(PackageCredit("Info-ZIP") 1018 .SetCopyright(COPYRIGHT_STRING 1019 "1990-2002 Info-ZIP. All rights reserved.")); 1020 // TODO: License! 1021 1022 // bzip2 copyrights 1023 _AddPackageCredit(PackageCredit("bzip2") 1024 .SetCopyright(COPYRIGHT_STRING 1025 "1996-2005 Julian R Seward. All rights reserved.")); 1026 // TODO: License! 1027 1028 // VIM copyrights 1029 _AddPackageCredit(PackageCredit("Vi IMproved") 1030 .SetCopyright(COPYRIGHT_STRING "Bram Moolenaar et al.")); 1031 // TODO: License! 1032 1033 // lp_solve copyrights 1034 _AddPackageCredit(PackageCredit("lp_solve") 1035 .SetCopyright(COPYRIGHT_STRING 1036 "Michel Berkelaar, Kjell Eikland, Peter Notebaert") 1037 .SetLicense("GNU LGPL v2.1") 1038 .SetURL("http://lpsolve.sourceforge.net/")); 1039 1040 // OpenEXR copyrights 1041 _AddPackageCredit(PackageCredit("OpenEXR") 1042 .SetCopyright(COPYRIGHT_STRING "2002-2005 Industrial Light & Magic, " 1043 "a division of Lucas Digital Ltd. LLC.")); 1044 // TODO: License! 1045 1046 // Bullet copyrights 1047 _AddPackageCredit(PackageCredit("Bullet") 1048 .SetCopyright(COPYRIGHT_STRING "2003-2008 Erwin Coumans") 1049 .SetURL("http://www.bulletphysics.com")); 1050 // TODO: License! 1051 1052 // atftp copyrights 1053 _AddPackageCredit(PackageCredit("atftp") 1054 .SetCopyright(COPYRIGHT_STRING 1055 "2000 Jean-Pierre Lefebvre and Remi Lefebvre")); 1056 // TODO: License! 1057 1058 // Netcat copyrights 1059 _AddPackageCredit(PackageCredit("Netcat") 1060 .SetCopyright(COPYRIGHT_STRING "1996 Hobbit")); 1061 // TODO: License! 1062 1063 // acpica copyrights 1064 _AddPackageCredit(PackageCredit("acpica") 1065 .SetCopyright(COPYRIGHT_STRING "1999-2006 Intel Corp.")); 1066 // TODO: License! 1067 1068 // unrar copyrights 1069 _AddPackageCredit(PackageCredit("unrar") 1070 .SetCopyright(COPYRIGHT_STRING "2002-2008 Alexander L. Roshal. " 1071 "All rights reserved.") 1072 .SetURL("http://www.rarlab.com")); 1073 // TODO: License! 1074 1075 // libpng copyrights 1076 _AddPackageCredit(PackageCredit("libpng") 1077 .SetCopyright(COPYRIGHT_STRING "2004, 2006-2008 Glenn " 1078 "Randers-Pehrson.")); 1079 // TODO: License! 1080 1081 // libprint copyrights 1082 _AddPackageCredit(PackageCredit("libprint") 1083 .SetCopyright(COPYRIGHT_STRING 1084 "1999-2000 Y.Takagi. All rights reserved.")); 1085 // TODO: License! 1086 1087 // cortex copyrights 1088 _AddPackageCredit(PackageCredit("Cortex") 1089 .SetCopyright(COPYRIGHT_STRING "1999-2000 Eric Moon.")); 1090 // TODO: License! 1091 1092 // FluidSynth copyrights 1093 _AddPackageCredit(PackageCredit("FluidSynth") 1094 .SetCopyright(COPYRIGHT_STRING "2003 Peter Hanappe and others.")); 1095 // TODO: License! 1096 1097 // CannaIM copyrights 1098 _AddPackageCredit(PackageCredit("CannaIM") 1099 .SetCopyright(COPYRIGHT_STRING "1999 Masao Kawamura.")); 1100 // TODO: License! 1101 1102 // libxml2, libxslt, libexslt copyrights 1103 _AddPackageCredit(PackageCredit("libxml2, libxslt") 1104 .SetCopyright(COPYRIGHT_STRING 1105 "1998-2003 Daniel Veillard. All rights reserved.")); 1106 // TODO: License! 1107 1108 _AddPackageCredit(PackageCredit("libexslt") 1109 .SetCopyright(COPYRIGHT_STRING 1110 "2001-2002 Thomas Broyer, Charlie " 1111 "Bozeman and Daniel Veillard. All rights reserved.")); 1112 // TODO: License! 1113 1114 // Xiph.org Foundation copyrights 1115 _AddPackageCredit(PackageCredit("Xiph.org Foundation") 1116 .SetCopyrights("libvorbis, libogg, libtheora, libspeex", 1117 COPYRIGHT_STRING "1994-2008 Xiph.Org. " 1118 "All rights reserved.", 1119 NULL) 1120 .SetURL("http://www.xiph.org")); 1121 // TODO: License! 1122 1123 // The Tcpdump Group 1124 _AddPackageCredit(PackageCredit("The Tcpdump Group") 1125 .SetCopyright("tcpdump, libpcap") 1126 .SetURL("http://www.tcpdump.org")); 1127 // TODO: License! 1128 1129 // Matroska 1130 _AddPackageCredit(PackageCredit("libmatroska") 1131 .SetCopyright(COPYRIGHT_STRING "2002-2003 Steve Lhomme. " 1132 "All rights reserved.") 1133 .SetURL("http://www.matroska.org")); 1134 // TODO: License! 1135 1136 // BColorQuantizer (originally CQuantizer code) 1137 _AddPackageCredit(PackageCredit("CQuantizer") 1138 .SetCopyright(COPYRIGHT_STRING "1996-1997 Jeff Prosise. " 1139 "All rights reserved.")); 1140 // TODO: License! 1141 1142 // MAPM (Mike's Arbitrary Precision Math Library) used by DeskCalc 1143 _AddPackageCredit(PackageCredit("MAPM") 1144 .SetCopyright(COPYRIGHT_STRING 1145 "1999-2007 Michael C. Ring. All rights reserved.") 1146 .SetURL("http://tc.umn.edu/~ringx004")); 1147 // TODO: License! 1148 1149 // MkDepend 1.7 copyright (Makefile dependency generator) 1150 _AddPackageCredit(PackageCredit("MkDepend") 1151 .SetCopyright(COPYRIGHT_STRING "1995-2001 Lars Düning. " 1152 "All rights reserved.")); 1153 // TODO: License! 1154 1155 // libhttpd copyright (used as Poorman backend) 1156 _AddPackageCredit(PackageCredit("libhttpd") 1157 .SetCopyright(COPYRIGHT_STRING 1158 "1995,1998,1999,2000,2001 by " 1159 "Jef Poskanzer. All rights reserved.") 1160 .SetLicense("LibHTTPd") 1161 .SetURL("http://www.acme.com/software/thttpd/")); 1162 1163 #ifdef __INTEL__ 1164 // Udis86 copyrights 1165 _AddPackageCredit(PackageCredit("Udis86") 1166 .SetCopyright(COPYRIGHT_STRING "2002, 2003, 2004 Vivek Mohan. " 1167 "All rights reserved.") 1168 .SetURL("http://udis86.sourceforge.net")); 1169 // TODO: License! 1170 #endif 1171 1172 _AddCopyrightsFromAttribute(); 1173 _AddPackageCreditEntries(); 1174 1175 return new CropView(creditsScroller, 0, 1, 1, 1); 1176 } 1177 1178 1179 status_t 1180 AboutView::_GetLicensePath(const char* license, BPath& path) 1181 { 1182 static const directory_which directoryConstants[] = { 1183 B_USER_DATA_DIRECTORY, 1184 B_COMMON_DATA_DIRECTORY, 1185 B_SYSTEM_DATA_DIRECTORY 1186 }; 1187 static const int dirCount = 3; 1188 1189 for (int i = 0; i < dirCount; i++) { 1190 struct stat st; 1191 status_t error = find_directory(directoryConstants[i], &path); 1192 if (error == B_OK && path.Append("licenses") == B_OK 1193 && path.Append(license) == B_OK 1194 && lstat(path.Path(), &st) == 0) { 1195 return B_OK; 1196 } 1197 } 1198 1199 path.Unset(); 1200 return B_ENTRY_NOT_FOUND; 1201 } 1202 1203 1204 void 1205 AboutView::_AddCopyrightsFromAttribute() 1206 { 1207 #ifdef __HAIKU__ 1208 // open the app executable file 1209 char appPath[B_PATH_NAME_LENGTH]; 1210 int appFD; 1211 if (BPrivate::get_app_path(appPath) != B_OK 1212 || (appFD = open(appPath, O_RDONLY)) < 0) { 1213 return; 1214 } 1215 1216 // open the attribute 1217 int attrFD = fs_fopen_attr(appFD, "COPYRIGHTS", B_STRING_TYPE, O_RDONLY); 1218 close(appFD); 1219 if (attrFD < 0) 1220 return; 1221 1222 // attach it to a FILE 1223 FILE* attrFile = fdopen(attrFD, "r"); 1224 if (attrFile == NULL) { 1225 close(attrFD); 1226 return; 1227 } 1228 CObjectDeleter<FILE, int> _(attrFile, fclose); 1229 1230 // read and parse the copyrights 1231 BMessage package; 1232 BString fieldName; 1233 BString fieldValue; 1234 char lineBuffer[LINE_MAX]; 1235 while (char* line = fgets(lineBuffer, sizeof(lineBuffer), attrFile)) { 1236 // chop off line break 1237 size_t lineLen = strlen(line); 1238 if (lineLen > 0 && line[lineLen - 1] == '\n') 1239 line[--lineLen] = '\0'; 1240 1241 // flush previous field, if a new field begins, otherwise append 1242 if (lineLen == 0 || !isspace(line[0])) { 1243 // new field -- flush the previous one 1244 if (fieldName.Length() > 0) { 1245 fieldValue = trim_string(fieldValue.String(), 1246 fieldValue.Length()); 1247 package.AddString(fieldName.String(), fieldValue); 1248 fieldName = ""; 1249 } 1250 } else if (fieldName.Length() > 0) { 1251 // append to current field 1252 fieldValue += line; 1253 continue; 1254 } else { 1255 // bogus line -- ignore 1256 continue; 1257 } 1258 1259 if (lineLen == 0) 1260 continue; 1261 1262 // parse new field 1263 char* colon = strchr(line, ':'); 1264 if (colon == NULL) { 1265 // bogus line -- ignore 1266 continue; 1267 } 1268 1269 fieldName.SetTo(line, colon - line); 1270 fieldName = trim_string(line, colon - line); 1271 if (fieldName.Length() == 0) { 1272 // invalid field name 1273 continue; 1274 } 1275 1276 fieldValue = colon + 1; 1277 1278 if (fieldName == "Package") { 1279 // flush the current package 1280 _AddPackageCredit(PackageCredit(package)); 1281 package.MakeEmpty(); 1282 } 1283 } 1284 1285 // flush current package 1286 _AddPackageCredit(PackageCredit(package)); 1287 #endif 1288 } 1289 1290 1291 void 1292 AboutView::_AddPackageCreditEntries() 1293 { 1294 for (PackageCreditMap::iterator it = fPackageCredits.begin(); 1295 it != fPackageCredits.end(); ++it) { 1296 PackageCredit* package = it->second; 1297 1298 BString text(package->CopyrightAt(0)); 1299 int32 count = package->CountCopyrights(); 1300 for (int32 i = 1; i < count; i++) 1301 text << "\n" << package->CopyrightAt(i); 1302 1303 AddCopyrightEntry(package->PackageName(), text.String(), 1304 package->Licenses(), package->URL()); 1305 } 1306 } 1307 1308 1309 void 1310 AboutView::_AddPackageCredit(const PackageCredit& package) 1311 { 1312 if (!package.IsValid()) 1313 return; 1314 1315 PackageCreditMap::iterator it = fPackageCredits.find(package.PackageName()); 1316 if (it != fPackageCredits.end()) { 1317 // If the new package credit isn't "better" than the old one, ignore it. 1318 PackageCredit* oldPackage = it->second; 1319 if (!package.IsBetterThan(*oldPackage)) 1320 return; 1321 1322 // replace the old credit 1323 fPackageCredits.erase(it); 1324 delete oldPackage; 1325 } 1326 1327 fPackageCredits[package.PackageName()] = new PackageCredit(package); 1328 } 1329 1330 1331 // #pragma mark - 1332 1333 1334 static const char * 1335 MemUsageToString(char string[], size_t size, system_info *info) 1336 { 1337 snprintf(string, size, "%d MB total, %d MB used (%d%%)", 1338 int(info->max_pages / 256.0f + 0.5f), 1339 int(info->used_pages / 256.0f + 0.5f), 1340 int(100 * info->used_pages / info->max_pages)); 1341 1342 return string; 1343 } 1344 1345 1346 static const char * 1347 UptimeToString(char string[], size_t size) 1348 { 1349 int64 days, hours, minutes, seconds, remainder; 1350 int64 systime = system_time(); 1351 1352 days = systime / 86400000000LL; 1353 remainder = systime % 86400000000LL; 1354 1355 hours = remainder / 3600000000LL; 1356 remainder = remainder % 3600000000LL; 1357 1358 minutes = remainder / 60000000; 1359 remainder = remainder % 60000000; 1360 1361 seconds = remainder / 1000000; 1362 1363 char *str = string; 1364 if (days) { 1365 str += snprintf(str, size, "%lld day%s",days, days > 1 ? "s" : ""); 1366 } 1367 if (hours) { 1368 str += snprintf(str, size - strlen(string), "%s%lld hour%s", 1369 str != string ? ", " : "", 1370 hours, hours > 1 ? "s" : ""); 1371 } 1372 if (minutes) { 1373 str += snprintf(str, size - strlen(string), "%s%lld minute%s", 1374 str != string ? ", " : "", 1375 minutes, minutes > 1 ? "s" : ""); 1376 } 1377 1378 if (seconds || str == string) { 1379 // Haiku would be well-known to boot very fast. 1380 // Let's be ready to handle below minute uptime, zero second included ;-) 1381 str += snprintf(str, size - strlen(string), "%s%lld second%s", 1382 str != string ? ", " : "", 1383 seconds, seconds > 1 ? "s" : ""); 1384 } 1385 1386 return string; 1387 } 1388 1389 1390 int 1391 main() 1392 { 1393 AboutApp app; 1394 app.Run(); 1395 return 0; 1396 } 1397 1398