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 "Mark Erben\n" 794 "Christian Fasshauer\n" 795 "Andreas Färber\n" 796 "Marc Flerackers\n" 797 "Michele Frau (zuMi)\n" 798 "Matthijs Hollemans\n" 799 "Mathew Hounsell\n" 800 "Morgan Howe\n" 801 "Ma Jie\n" 802 "Carwyn Jones\n" 803 "Vasilis Kaoutsis\n" 804 "James Kim\n" 805 "Shintaro Kinugawa\n" 806 "Jan Klötzke\n" 807 "Kurtis Kopf\n" 808 "Tomáš Kučera\n" 809 "Luboš Kulič\n" 810 "Elad Lahav\n" 811 "Anthony Lee\n" 812 "Santiago Lema\n" 813 "Raynald Lesieur\n" 814 "Oscar Lesta\n" 815 "Jerome Leveque\n" 816 "Christof Lutteroth\n" 817 "Graham MacDonald\n" 818 "Brecht Machiels\n" 819 "Jan Matějek\n" 820 "Brian Matzon\n" 821 "Christopher ML Zumwalt May\n" 822 "Andrew McCall\n" 823 "Scott McCreary\n" 824 "Marius Middelthon\n" 825 "Marco Minutoli\n" 826 "Misza\n" 827 "MrSiggler\n" 828 "Alan Murta\n" 829 "Raghuram Nagireddy\n" 830 "Jeroen Oortwijn (idefix)\n" 831 "Pahtz\n" 832 "Michael Paine\n" 833 "Adrian Panasiuk\n" 834 "Francesco Piccinno\n" 835 "David Powell\n" 836 "Jeremy Rand\n" 837 "Hartmut Reh\n" 838 "Daniel Reinhold\n" 839 "Chris Roberts\n" 840 "Samuel Rodriguez Perez\n" 841 "Thomas Roell\n" 842 "Rafael Romo\n" 843 "Ralf Schülke\n" 844 "Reznikov Sergei\n" 845 "Zousar Shaker\n" 846 "Daniel Switkin\n" 847 "Atsushi Takamatsu\n" 848 "James Urquhart\n" 849 "Jason Vandermark\n" 850 "Sandor Vroemisse\n" 851 "Denis Washington\n" 852 "Ulrich Wimboeck\n" 853 "Johannes Wischert\n" 854 "James Woodcock\n" 855 "Gerald Zajac\n" 856 "Łukasz Zemczak\n" 857 "JiSheng Zhang\n" 858 "Zhao Shuai\n" 859 "\n" B_UTF8_ELLIPSIS " and probably some more we forgot to mention " 860 "(sorry!)" 861 "\n\n"); 862 863 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuOrange); 864 fCreditsView->Insert("Special Thanks To:\n"); 865 866 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 867 fCreditsView->Insert("Travis Geiselbrecht (and his NewOS kernel)\n"); 868 fCreditsView->Insert("Michael Phipps (project founder)\n\n"); 869 fCreditsView->Insert("The Haiku-Ports Team\n"); 870 fCreditsView->Insert("The Haikuware Team and their Bounty Program\n"); 871 fCreditsView->Insert("The BeGeistert Team\n\n"); 872 fCreditsView->Insert("... and the many community members making " 873 "donations!\n\n"); 874 875 // copyrights for various projects we use 876 877 BPath mitPath; 878 _GetLicensePath("MIT", mitPath); 879 880 font.SetSize(be_bold_font->Size() + 4); 881 font.SetFace(B_BOLD_FACE); 882 fCreditsView->SetFontAndColor(&font, B_FONT_ALL, &kHaikuGreen); 883 fCreditsView->Insert("\nCopyrights\n\n"); 884 885 fCreditsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kDarkGrey); 886 fCreditsView->Insert("[Click a license name to read the respective " 887 "license.]\n\n"); 888 889 // Haiku license 890 fCreditsView->Insert("The code that is unique to Haiku, especially the " 891 "kernel and all code that applications may link against, is " 892 "distributed under the terms of the "); 893 fCreditsView->InsertHyperText("MIT license", 894 new OpenFileAction(mitPath.Path())); 895 fCreditsView->Insert(". Some system libraries contain third party code " 896 "distributed under the LGPL license. You can find the copyrights " 897 "to third party code below.\n\n"); 898 899 // GNU copyrights 900 AddCopyrightEntry("The GNU Project", 901 "Contains software from the GNU Project, " 902 "released under the GPL and LGPL licences:\n" 903 "GNU C Library, " 904 "GNU coretools, diffutils, findutils, " 905 "sharutils, gawk, bison, m4, make, " 906 "gdb, wget, ncurses, termcap, " 907 "Bourne Again Shell.\n" 908 COPYRIGHT_STRING "The Free Software Foundation.", 909 StringVector("GNU LGPL v2.1", "GNU GPL v2", "GNU GPL v3", NULL), 910 "http://www.gnu.org"); 911 912 // FreeBSD copyrights 913 AddCopyrightEntry("The FreeBSD Project", 914 "Contains software from the FreeBSD Project, " 915 "released under the BSD licence:\n" 916 "cal, ftpd, ping, telnet, " 917 "telnetd, traceroute\n" 918 COPYRIGHT_STRING "1994-2008 The FreeBSD Project. " 919 "All rights reserved.", 920 "http://www.freebsd.org"); 921 // TODO: License! 922 923 // NetBSD copyrights 924 AddCopyrightEntry("The NetBSD Project", 925 "Contains software developed by the NetBSD, " 926 "Foundation, Inc. and its contributors:\n" 927 "ftp, tput\n" 928 COPYRIGHT_STRING "1996-2008 The NetBSD Foundation, Inc. " 929 "All rights reserved.", 930 "http://www.netbsd.org"); 931 // TODO: License! 932 933 // FFMpeg copyrights 934 _AddPackageCredit(PackageCredit("FFMpeg libavcodec") 935 .SetCopyright(COPYRIGHT_STRING "2000-2007 Fabrice Bellard, et al.") 936 .SetURL("http://www.ffmpeg.org")); 937 // TODO: License! 938 939 // AGG copyrights 940 _AddPackageCredit(PackageCredit("AntiGrain Geometry") 941 .SetCopyright(COPYRIGHT_STRING "2002-2006 Maxim Shemanarev (McSeem).") 942 .SetURL("http://www.antigrain.com")); 943 // TODO: License! 944 945 // PDFLib copyrights 946 _AddPackageCredit(PackageCredit("PDFLib") 947 .SetCopyright(COPYRIGHT_STRING "1997-2006 PDFlib GmbH and Thomas Merz. " 948 "All rights reserved.\n" 949 "PDFlib and PDFlib logo are registered trademarks of PDFlib GmbH.") 950 .SetURL("http://www.pdflib.com")); 951 // TODO: License! 952 953 // FreeType copyrights 954 _AddPackageCredit(PackageCredit("FreeType2") 955 .SetCopyright("Portions of this software are copyright " 956 B_UTF8_COPYRIGHT " 1996-2006 " 957 "The FreeType Project. All rights reserved.") 958 .SetURL("http://www.freetype.org")); 959 // TODO: License! 960 961 // Mesa3D (http://www.mesa3d.org) copyrights 962 _AddPackageCredit(PackageCredit("Mesa") 963 .SetCopyright(COPYRIGHT_STRING "1999-2006 Brian Paul. " 964 "Mesa3D project. All rights reserved.") 965 .SetURL("http://www.mesa3d.org")); 966 // TODO: License! 967 968 // SGI's GLU implementation copyrights 969 _AddPackageCredit(PackageCredit("GLU") 970 .SetCopyright(COPYRIGHT_STRING 971 "1991-2000 Silicon Graphics, Inc. " 972 "SGI's Software FreeB license. All rights reserved.")); 973 // TODO: License! 974 975 // GLUT implementation copyrights 976 _AddPackageCredit(PackageCredit("GLUT") 977 .SetCopyrights(COPYRIGHT_STRING "1994-1997 Mark Kilgard. " 978 "All rights reserved.", 979 COPYRIGHT_STRING "1997 Be Inc.", 980 COPYRIGHT_STRING "1999 Jake Hamby.", 981 NULL)); 982 // TODO: License! 983 984 // OpenGroup & DEC (BRegion backend) copyright 985 _AddPackageCredit(PackageCredit("BRegion backend (XFree86)") 986 .SetCopyrights(COPYRIGHT_STRING "1987, 1988, 1998 The Open Group.", 987 COPYRIGHT_STRING "1987, 1988 Digital Equipment " 988 "Corporation, Maynard, Massachusetts.\n" 989 "All rights reserved.", 990 NULL)); 991 // TODO: License! 992 993 // Konatu font 994 _AddPackageCredit(PackageCredit("Konatu font") 995 .SetCopyright(COPYRIGHT_STRING "2002- MASUDA mitiya.\n" 996 "MIT license. All rights reserved.")); 997 // TODO: License! 998 999 // expat copyrights 1000 _AddPackageCredit(PackageCredit("expat") 1001 .SetCopyrights(COPYRIGHT_STRING 1002 "1998, 1999, 2000 Thai Open Source " 1003 "Software Center Ltd and Clark Cooper.", 1004 COPYRIGHT_STRING "2001, 2002, 2003 Expat maintainers.", 1005 NULL)); 1006 // TODO: License! 1007 1008 // zlib copyrights 1009 _AddPackageCredit(PackageCredit("zlib") 1010 .SetCopyright(COPYRIGHT_STRING 1011 "1995-2004 Jean-loup Gailly and Mark Adler.")); 1012 // TODO: License! 1013 1014 // zip copyrights 1015 _AddPackageCredit(PackageCredit("Info-ZIP") 1016 .SetCopyright(COPYRIGHT_STRING 1017 "1990-2002 Info-ZIP. All rights reserved.")); 1018 // TODO: License! 1019 1020 // bzip2 copyrights 1021 _AddPackageCredit(PackageCredit("bzip2") 1022 .SetCopyright(COPYRIGHT_STRING 1023 "1996-2005 Julian R Seward. All rights reserved.")); 1024 // TODO: License! 1025 1026 // VIM copyrights 1027 _AddPackageCredit(PackageCredit("Vi IMproved") 1028 .SetCopyright(COPYRIGHT_STRING "Bram Moolenaar et al.")); 1029 // TODO: License! 1030 1031 // lp_solve copyrights 1032 _AddPackageCredit(PackageCredit("lp_solve") 1033 .SetCopyright(COPYRIGHT_STRING 1034 "Michel Berkelaar, Kjell Eikland, Peter Notebaert") 1035 .SetLicense("GNU LGPL v2.1") 1036 .SetURL("http://lpsolve.sourceforge.net/")); 1037 1038 // OpenEXR copyrights 1039 _AddPackageCredit(PackageCredit("OpenEXR") 1040 .SetCopyright(COPYRIGHT_STRING "2002-2005 Industrial Light & Magic, " 1041 "a division of Lucas Digital Ltd. LLC.")); 1042 // TODO: License! 1043 1044 // Bullet copyrights 1045 _AddPackageCredit(PackageCredit("Bullet") 1046 .SetCopyright(COPYRIGHT_STRING "2003-2008 Erwin Coumans") 1047 .SetURL("http://www.bulletphysics.com")); 1048 // TODO: License! 1049 1050 // atftp copyrights 1051 _AddPackageCredit(PackageCredit("atftp") 1052 .SetCopyright(COPYRIGHT_STRING 1053 "2000 Jean-Pierre Lefebvre and Remi Lefebvre")); 1054 // TODO: License! 1055 1056 // Netcat copyrights 1057 _AddPackageCredit(PackageCredit("Netcat") 1058 .SetCopyright(COPYRIGHT_STRING "1996 Hobbit")); 1059 // TODO: License! 1060 1061 // acpica copyrights 1062 _AddPackageCredit(PackageCredit("acpica") 1063 .SetCopyright(COPYRIGHT_STRING "1999-2006 Intel Corp.")); 1064 // TODO: License! 1065 1066 // unrar copyrights 1067 _AddPackageCredit(PackageCredit("unrar") 1068 .SetCopyright(COPYRIGHT_STRING "2002-2008 Alexander L. Roshal. " 1069 "All rights reserved.") 1070 .SetURL("http://www.rarlab.com")); 1071 // TODO: License! 1072 1073 // libpng copyrights 1074 _AddPackageCredit(PackageCredit("libpng") 1075 .SetCopyright(COPYRIGHT_STRING "2004, 2006-2008 Glenn " 1076 "Randers-Pehrson.")); 1077 // TODO: License! 1078 1079 // libprint copyrights 1080 _AddPackageCredit(PackageCredit("libprint") 1081 .SetCopyright(COPYRIGHT_STRING 1082 "1999-2000 Y.Takagi. All rights reserved.")); 1083 // TODO: License! 1084 1085 // cortex copyrights 1086 _AddPackageCredit(PackageCredit("Cortex") 1087 .SetCopyright(COPYRIGHT_STRING "1999-2000 Eric Moon.")); 1088 // TODO: License! 1089 1090 // FluidSynth copyrights 1091 _AddPackageCredit(PackageCredit("FluidSynth") 1092 .SetCopyright(COPYRIGHT_STRING "2003 Peter Hanappe and others.")); 1093 // TODO: License! 1094 1095 // CannaIM copyrights 1096 _AddPackageCredit(PackageCredit("CannaIM") 1097 .SetCopyright(COPYRIGHT_STRING "1999 Masao Kawamura.")); 1098 // TODO: License! 1099 1100 // libxml2, libxslt, libexslt copyrights 1101 _AddPackageCredit(PackageCredit("libxml2, libxslt") 1102 .SetCopyright(COPYRIGHT_STRING 1103 "1998-2003 Daniel Veillard. All rights reserved.")); 1104 // TODO: License! 1105 1106 _AddPackageCredit(PackageCredit("libexslt") 1107 .SetCopyright(COPYRIGHT_STRING 1108 "2001-2002 Thomas Broyer, Charlie " 1109 "Bozeman and Daniel Veillard. All rights reserved.")); 1110 // TODO: License! 1111 1112 // Xiph.org Foundation copyrights 1113 _AddPackageCredit(PackageCredit("Xiph.org Foundation") 1114 .SetCopyrights("libvorbis, libogg, libtheora, libspeex", 1115 COPYRIGHT_STRING "1994-2008 Xiph.Org. " 1116 "All rights reserved.", 1117 NULL) 1118 .SetURL("http://www.xiph.org")); 1119 // TODO: License! 1120 1121 // The Tcpdump Group 1122 _AddPackageCredit(PackageCredit("The Tcpdump Group") 1123 .SetCopyright("tcpdump, libpcap") 1124 .SetURL("http://www.tcpdump.org")); 1125 // TODO: License! 1126 1127 // Matroska 1128 _AddPackageCredit(PackageCredit("libmatroska") 1129 .SetCopyright(COPYRIGHT_STRING "2002-2003 Steve Lhomme. " 1130 "All rights reserved.") 1131 .SetURL("http://www.matroska.org")); 1132 // TODO: License! 1133 1134 // BColorQuantizer (originally CQuantizer code) 1135 _AddPackageCredit(PackageCredit("CQuantizer") 1136 .SetCopyright(COPYRIGHT_STRING "1996-1997 Jeff Prosise. " 1137 "All rights reserved.")); 1138 // TODO: License! 1139 1140 // MAPM (Mike's Arbitrary Precision Math Library) used by DeskCalc 1141 _AddPackageCredit(PackageCredit("MAPM") 1142 .SetCopyright(COPYRIGHT_STRING 1143 "1999-2007 Michael C. Ring. All rights reserved.") 1144 .SetURL("http://tc.umn.edu/~ringx004")); 1145 // TODO: License! 1146 1147 // MkDepend 1.7 copyright (Makefile dependency generator) 1148 _AddPackageCredit(PackageCredit("MkDepend") 1149 .SetCopyright(COPYRIGHT_STRING "1995-2001 Lars Düning. " 1150 "All rights reserved.")); 1151 // TODO: License! 1152 1153 // libhttpd copyright (used as Poorman backend) 1154 _AddPackageCredit(PackageCredit("libhttpd") 1155 .SetCopyright(COPYRIGHT_STRING 1156 "1995,1998,1999,2000,2001 by " 1157 "Jef Poskanzer. All rights reserved.") 1158 .SetLicense("LibHTTPd") 1159 .SetURL("http://www.acme.com/software/thttpd/")); 1160 1161 #ifdef __INTEL__ 1162 // Udis86 copyrights 1163 _AddPackageCredit(PackageCredit("Udis86") 1164 .SetCopyright(COPYRIGHT_STRING "2002, 2003, 2004 Vivek Mohan. " 1165 "All rights reserved.") 1166 .SetURL("http://udis86.sourceforge.net")); 1167 // TODO: License! 1168 #endif 1169 1170 _AddCopyrightsFromAttribute(); 1171 _AddPackageCreditEntries(); 1172 1173 return new CropView(creditsScroller, 0, 1, 1, 1); 1174 } 1175 1176 1177 status_t 1178 AboutView::_GetLicensePath(const char* license, BPath& path) 1179 { 1180 static const directory_which directoryConstants[] = { 1181 B_USER_DATA_DIRECTORY, 1182 B_COMMON_DATA_DIRECTORY, 1183 B_SYSTEM_DATA_DIRECTORY 1184 }; 1185 static const int dirCount = 3; 1186 1187 for (int i = 0; i < dirCount; i++) { 1188 struct stat st; 1189 status_t error = find_directory(directoryConstants[i], &path); 1190 if (error == B_OK && path.Append("licenses") == B_OK 1191 && path.Append(license) == B_OK 1192 && lstat(path.Path(), &st) == 0) { 1193 return B_OK; 1194 } 1195 } 1196 1197 path.Unset(); 1198 return B_ENTRY_NOT_FOUND; 1199 } 1200 1201 1202 void 1203 AboutView::_AddCopyrightsFromAttribute() 1204 { 1205 #ifdef __HAIKU__ 1206 // open the app executable file 1207 char appPath[B_PATH_NAME_LENGTH]; 1208 int appFD; 1209 if (BPrivate::get_app_path(appPath) != B_OK 1210 || (appFD = open(appPath, O_RDONLY)) < 0) { 1211 return; 1212 } 1213 1214 // open the attribute 1215 int attrFD = fs_fopen_attr(appFD, "COPYRIGHTS", B_STRING_TYPE, O_RDONLY); 1216 close(appFD); 1217 if (attrFD < 0) 1218 return; 1219 1220 // attach it to a FILE 1221 FILE* attrFile = fdopen(attrFD, "r"); 1222 if (attrFile == NULL) { 1223 close(attrFD); 1224 return; 1225 } 1226 CObjectDeleter<FILE, int> _(attrFile, fclose); 1227 1228 // read and parse the copyrights 1229 BMessage package; 1230 BString fieldName; 1231 BString fieldValue; 1232 char lineBuffer[LINE_MAX]; 1233 while (char* line = fgets(lineBuffer, sizeof(lineBuffer), attrFile)) { 1234 // chop off line break 1235 size_t lineLen = strlen(line); 1236 if (lineLen > 0 && line[lineLen - 1] == '\n') 1237 line[--lineLen] = '\0'; 1238 1239 // flush previous field, if a new field begins, otherwise append 1240 if (lineLen == 0 || !isspace(line[0])) { 1241 // new field -- flush the previous one 1242 if (fieldName.Length() > 0) { 1243 fieldValue = trim_string(fieldValue.String(), 1244 fieldValue.Length()); 1245 package.AddString(fieldName.String(), fieldValue); 1246 fieldName = ""; 1247 } 1248 } else if (fieldName.Length() > 0) { 1249 // append to current field 1250 fieldValue += line; 1251 continue; 1252 } else { 1253 // bogus line -- ignore 1254 continue; 1255 } 1256 1257 if (lineLen == 0) 1258 continue; 1259 1260 // parse new field 1261 char* colon = strchr(line, ':'); 1262 if (colon == NULL) { 1263 // bogus line -- ignore 1264 continue; 1265 } 1266 1267 fieldName.SetTo(line, colon - line); 1268 fieldName = trim_string(line, colon - line); 1269 if (fieldName.Length() == 0) { 1270 // invalid field name 1271 continue; 1272 } 1273 1274 fieldValue = colon + 1; 1275 1276 if (fieldName == "Package") { 1277 // flush the current package 1278 _AddPackageCredit(PackageCredit(package)); 1279 package.MakeEmpty(); 1280 } 1281 } 1282 1283 // flush current package 1284 _AddPackageCredit(PackageCredit(package)); 1285 #endif 1286 } 1287 1288 1289 void 1290 AboutView::_AddPackageCreditEntries() 1291 { 1292 for (PackageCreditMap::iterator it = fPackageCredits.begin(); 1293 it != fPackageCredits.end(); ++it) { 1294 PackageCredit* package = it->second; 1295 1296 BString text(package->CopyrightAt(0)); 1297 int32 count = package->CountCopyrights(); 1298 for (int32 i = 1; i < count; i++) 1299 text << "\n" << package->CopyrightAt(i); 1300 1301 AddCopyrightEntry(package->PackageName(), text.String(), 1302 package->Licenses(), package->URL()); 1303 } 1304 } 1305 1306 1307 void 1308 AboutView::_AddPackageCredit(const PackageCredit& package) 1309 { 1310 if (!package.IsValid()) 1311 return; 1312 1313 PackageCreditMap::iterator it = fPackageCredits.find(package.PackageName()); 1314 if (it != fPackageCredits.end()) { 1315 // If the new package credit isn't "better" than the old one, ignore it. 1316 PackageCredit* oldPackage = it->second; 1317 if (!package.IsBetterThan(*oldPackage)) 1318 return; 1319 1320 // replace the old credit 1321 fPackageCredits.erase(it); 1322 delete oldPackage; 1323 } 1324 1325 fPackageCredits[package.PackageName()] = new PackageCredit(package); 1326 } 1327 1328 1329 // #pragma mark - 1330 1331 1332 static const char * 1333 MemUsageToString(char string[], size_t size, system_info *info) 1334 { 1335 snprintf(string, size, "%d MB total, %d MB used (%d%%)", 1336 int(info->max_pages / 256.0f + 0.5f), 1337 int(info->used_pages / 256.0f + 0.5f), 1338 int(100 * info->used_pages / info->max_pages)); 1339 1340 return string; 1341 } 1342 1343 1344 static const char * 1345 UptimeToString(char string[], size_t size) 1346 { 1347 int64 days, hours, minutes, seconds, remainder; 1348 int64 systime = system_time(); 1349 1350 days = systime / 86400000000LL; 1351 remainder = systime % 86400000000LL; 1352 1353 hours = remainder / 3600000000LL; 1354 remainder = remainder % 3600000000LL; 1355 1356 minutes = remainder / 60000000; 1357 remainder = remainder % 60000000; 1358 1359 seconds = remainder / 1000000; 1360 1361 char *str = string; 1362 if (days) { 1363 str += snprintf(str, size, "%lld day%s",days, days > 1 ? "s" : ""); 1364 } 1365 if (hours) { 1366 str += snprintf(str, size - strlen(string), "%s%lld hour%s", 1367 str != string ? ", " : "", 1368 hours, hours > 1 ? "s" : ""); 1369 } 1370 if (minutes) { 1371 str += snprintf(str, size - strlen(string), "%s%lld minute%s", 1372 str != string ? ", " : "", 1373 minutes, minutes > 1 ? "s" : ""); 1374 } 1375 1376 if (seconds || str == string) { 1377 // Haiku would be well-known to boot very fast. 1378 // Let's be ready to handle below minute uptime, zero second included ;-) 1379 str += snprintf(str, size - strlen(string), "%s%lld second%s", 1380 str != string ? ", " : "", 1381 seconds, seconds > 1 ? "s" : ""); 1382 } 1383 1384 return string; 1385 } 1386 1387 1388 int 1389 main() 1390 { 1391 AboutApp app; 1392 app.Run(); 1393 return 0; 1394 } 1395 1396