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