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