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