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