xref: /haiku/src/apps/networkstatus/NetworkStatusView.cpp (revision e53f0019b57484c9fe0b24371d9c8520b52af57d)
1 /*
2  * Copyright 2006-2012, Haiku, Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Authors:
6  *		Dario Casalinuovo
7  *		Axel Dörfler, axeld@pinc-software.de
8  *		Rene Gollent, rene@gollent.com
9  *		Hugo Santos, hugosantos@gmail.com
10  */
11 
12 
13 #include "NetworkStatusView.h"
14 
15 #include <set>
16 
17 #include <arpa/inet.h>
18 #include <net/if.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <sys/socket.h>
23 #include <sys/sockio.h>
24 #include <unistd.h>
25 
26 #include <Alert.h>
27 #include <Application.h>
28 #include <Catalog.h>
29 #include <Bitmap.h>
30 #include <Deskbar.h>
31 #include <Dragger.h>
32 #include <Drivers.h>
33 #include <IconUtils.h>
34 #include <Locale.h>
35 #include <MenuItem.h>
36 #include <MessageRunner.h>
37 #include <NetworkDevice.h>
38 #include <NetworkInterface.h>
39 #include <NetworkRoster.h>
40 #include <PopUpMenu.h>
41 #include <Resources.h>
42 #include <Roster.h>
43 #include <String.h>
44 #include <TextView.h>
45 
46 #include "NetworkStatus.h"
47 #include "NetworkStatusIcons.h"
48 #include "RadioView.h"
49 #include "WirelessNetworkMenuItem.h"
50 
51 
52 #undef B_TRANSLATION_CONTEXT
53 #define B_TRANSLATION_CONTEXT "NetworkStatusView"
54 
55 
56 static const char *kStatusDescriptions[] = {
57 	B_TRANSLATE("Unknown"),
58 	B_TRANSLATE("No link"),
59 	B_TRANSLATE("No stateful configuration"),
60 	B_TRANSLATE("Configuring"),
61 	B_TRANSLATE("Ready")
62 };
63 
64 extern "C" _EXPORT BView *instantiate_deskbar_item(void);
65 
66 
67 const uint32 kMsgShowConfiguration = 'shcf';
68 const uint32 kMsgOpenNetworkPreferences = 'onwp';
69 const uint32 kMsgJoinNetwork = 'join';
70 
71 const uint32 kMinIconWidth = 16;
72 const uint32 kMinIconHeight = 16;
73 
74 
75 class SocketOpener {
76 public:
77 	SocketOpener()
78 	{
79 		fSocket = socket(AF_INET, SOCK_DGRAM, 0);
80 	}
81 
82 	~SocketOpener()
83 	{
84 		close(fSocket);
85 	}
86 
87 	status_t InitCheck()
88 	{
89 		return fSocket >= 0 ? B_OK : B_ERROR;
90 	}
91 
92 	operator int() const
93 	{
94 		return fSocket;
95 	}
96 
97 private:
98 	int	fSocket;
99 };
100 
101 
102 //	#pragma mark -
103 
104 
105 NetworkStatusView::NetworkStatusView(BRect frame, int32 resizingMode,
106 		bool inDeskbar)
107 	: BView(frame, kDeskbarItemName, resizingMode,
108 		B_WILL_DRAW | B_FRAME_EVENTS),
109 	fInDeskbar(inDeskbar)
110 {
111 	_Init();
112 
113 	if (!inDeskbar) {
114 		// we were obviously added to a standard window - let's add a dragger
115 		frame.OffsetTo(B_ORIGIN);
116 		frame.top = frame.bottom - 7;
117 		frame.left = frame.right - 7;
118 		BDragger* dragger = new BDragger(frame, this,
119 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
120 		AddChild(dragger);
121 	} else
122 		_Update();
123 }
124 
125 
126 NetworkStatusView::NetworkStatusView(BMessage* archive)
127 	: BView(archive),
128 	fInDeskbar(false)
129 {
130 	app_info info;
131 	if (be_app->GetAppInfo(&info) == B_OK
132 		&& !strcasecmp(info.signature, "application/x-vnd.Be-TSKB"))
133 		fInDeskbar = true;
134 
135 	_Init();
136 }
137 
138 
139 NetworkStatusView::~NetworkStatusView()
140 {
141 }
142 
143 
144 void
145 NetworkStatusView::_Init()
146 {
147 	for (int i = 0; i < kStatusCount; i++) {
148 		fTrayIcons[i] = NULL;
149 		fNotifyIcons[i] = NULL;
150 	}
151 
152 	_UpdateBitmaps();
153 }
154 
155 
156 void
157 NetworkStatusView::_UpdateBitmaps()
158 {
159 	for (int i = 0; i < kStatusCount; i++) {
160 		delete fTrayIcons[i];
161 		delete fNotifyIcons[i];
162 		fTrayIcons[i] = NULL;
163 		fNotifyIcons[i] = NULL;
164 	}
165 
166 	image_info info;
167 	if (our_image(info) != B_OK)
168 		return;
169 
170 	BFile file(info.name, B_READ_ONLY);
171 	if (file.InitCheck() < B_OK)
172 		return;
173 
174 	BResources resources(&file);
175 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
176 	if (resources.InitCheck() < B_OK)
177 		return;
178 #endif
179 
180 	for (int i = 0; i < kStatusCount; i++) {
181 		const void* data = NULL;
182 		size_t size;
183 		data = resources.LoadResource(B_VECTOR_ICON_TYPE,
184 			kNetworkStatusNoDevice + i, &size);
185 		if (data != NULL) {
186 			// Scale main tray icon
187 			BBitmap* trayIcon = new BBitmap(Bounds(), B_RGBA32);
188 			if (trayIcon->InitCheck() == B_OK
189 				&& BIconUtils::GetVectorIcon((const uint8 *)data,
190 					size, trayIcon) == B_OK) {
191 				fTrayIcons[i] = trayIcon;
192 			} else
193 				delete trayIcon;
194 
195 			// Scale notification icon
196 			BBitmap* notifyIcon = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32);
197 			if (notifyIcon->InitCheck() == B_OK
198 				&& BIconUtils::GetVectorIcon((const uint8 *)data,
199 					size, notifyIcon) == B_OK) {
200 				fNotifyIcons[i] = notifyIcon;
201 			} else
202 				delete notifyIcon;
203 		}
204 	}
205 }
206 
207 
208 void
209 NetworkStatusView::_Quit()
210 {
211 	if (fInDeskbar) {
212 		BDeskbar deskbar;
213 		deskbar.RemoveItem(kDeskbarItemName);
214 	} else
215 		be_app->PostMessage(B_QUIT_REQUESTED);
216 }
217 
218 
219 NetworkStatusView *
220 NetworkStatusView::Instantiate(BMessage* archive)
221 {
222 	if (!validate_instantiation(archive, "NetworkStatusView"))
223 		return NULL;
224 
225 	return new NetworkStatusView(archive);
226 }
227 
228 
229 status_t
230 NetworkStatusView::Archive(BMessage* archive, bool deep) const
231 {
232 	status_t status = BView::Archive(archive, deep);
233 	if (status == B_OK)
234 		status = archive->AddString("add_on", kSignature);
235 	if (status == B_OK)
236 		status = archive->AddString("class", "NetworkStatusView");
237 
238 	return status;
239 }
240 
241 
242 void
243 NetworkStatusView::AttachedToWindow()
244 {
245 	BView::AttachedToWindow();
246 	if (Parent() != NULL) {
247 		if ((Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0)
248 			SetViewColor(B_TRANSPARENT_COLOR);
249 		else
250 			SetViewColor(Parent()->ViewColor());
251 	} else
252 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
253 
254 	SetLowColor(ViewColor());
255 
256 	start_watching_network(
257 		B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this);
258 
259 	_Update();
260 }
261 
262 
263 void
264 NetworkStatusView::DetachedFromWindow()
265 {
266 	stop_watching_network(this);
267 }
268 
269 
270 void
271 NetworkStatusView::MessageReceived(BMessage* message)
272 {
273 	switch (message->what) {
274 		case B_NETWORK_MONITOR:
275 			_Update();
276 			break;
277 
278 		case kMsgShowConfiguration:
279 			_ShowConfiguration(message);
280 			break;
281 
282 		case kMsgOpenNetworkPreferences:
283 			_OpenNetworksPreferences();
284 			break;
285 
286 		case kMsgJoinNetwork:
287 		{
288 			const char* deviceName;
289 			const char* name;
290 			if (message->FindString("device", &deviceName) == B_OK
291 				&& message->FindString("name", &name) == B_OK) {
292 				BNetworkDevice device(deviceName);
293 				status_t status = device.JoinNetwork(name);
294 				if (status != B_OK) {
295 					BString text
296 						= B_TRANSLATE("Could not join wireless network:\n");
297 					text << strerror(status);
298 					BAlert* alert = new BAlert(name, text.String(),
299 						B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
300 						B_STOP_ALERT);
301 					alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
302 					alert->Go(NULL);
303 				}
304 			}
305 			break;
306 		}
307 
308 		case B_ABOUT_REQUESTED:
309 			_AboutRequested();
310 			break;
311 
312 		case B_QUIT_REQUESTED:
313 			_Quit();
314 			break;
315 
316 		default:
317 			BView::MessageReceived(message);
318 	}
319 }
320 
321 
322 void
323 NetworkStatusView::FrameResized(float width, float height)
324 {
325 	_UpdateBitmaps();
326 	Invalidate();
327 }
328 
329 
330 void
331 NetworkStatusView::Draw(BRect updateRect)
332 {
333 	int32 status = kStatusUnknown;
334 	for (std::map<BString, int32>::const_iterator it
335 		= fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) {
336 		if (it->second > status)
337 			status = it->second;
338 	}
339 
340 	if (fTrayIcons[status] == NULL)
341 		return;
342 
343 	SetDrawingMode(B_OP_ALPHA);
344 	DrawBitmap(fTrayIcons[status]);
345 	SetDrawingMode(B_OP_COPY);
346 }
347 
348 
349 void
350 NetworkStatusView::_ShowConfiguration(BMessage* message)
351 {
352 	static const struct information_entry {
353 		const char*	label;
354 		int32		control;
355 	} kInformationEntries[] = {
356 		{ B_TRANSLATE("Address"), SIOCGIFADDR },
357 		{ B_TRANSLATE("Broadcast"), SIOCGIFBRDADDR },
358 		{ B_TRANSLATE("Netmask"), SIOCGIFNETMASK },
359 		{ NULL }
360 	};
361 
362 	SocketOpener socket;
363 	if (socket.InitCheck() != B_OK)
364 		return;
365 
366 	const char* name;
367 	if (message->FindString("interface", &name) != B_OK)
368 		return;
369 
370 	ifreq request;
371 	if (!_PrepareRequest(request, name))
372 		return;
373 
374 	BString text(B_TRANSLATE("%ifaceName information:\n"));
375 	text.ReplaceFirst("%ifaceName", name);
376 
377 	size_t boldLength = text.Length();
378 
379 	for (int i = 0; kInformationEntries[i].label; i++) {
380 		if (ioctl(socket, kInformationEntries[i].control, &request,
381 				sizeof(request)) < 0) {
382 			continue;
383 		}
384 
385 		char address[32];
386 		sockaddr_in* inetAddress = NULL;
387 		switch (kInformationEntries[i].control) {
388 			case SIOCGIFNETMASK:
389 				inetAddress = (sockaddr_in*)&request.ifr_mask;
390 				break;
391 			default:
392 				inetAddress = (sockaddr_in*)&request.ifr_addr;
393 				break;
394 		}
395 
396 		if (inet_ntop(AF_INET, &inetAddress->sin_addr, address,
397 				sizeof(address)) == NULL) {
398 			return;
399 		}
400 
401 		text << "\n" << kInformationEntries[i].label << ": " << address;
402 	}
403 
404 	BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK"));
405 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
406 	BTextView* view = alert->TextView();
407 	BFont font;
408 
409 	view->SetStylable(true);
410 	view->GetFont(&font);
411 	font.SetFace(B_BOLD_FACE);
412 	view->SetFontAndColor(0, boldLength, &font);
413 
414 	alert->Go(NULL);
415 }
416 
417 
418 void
419 NetworkStatusView::MouseDown(BPoint point)
420 {
421 	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
422 	menu->SetAsyncAutoDestruct(true);
423 	menu->SetFont(be_plain_font);
424 	BString wifiInterface;
425 	BNetworkDevice wifiDevice;
426 
427 	// Add interfaces
428 
429 	for (std::map<BString, int32>::const_iterator it
430 		= fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) {
431 		const BString& name = it->first;
432 
433 		BString label = name;
434 		label += ": ";
435 		label += kStatusDescriptions[
436 			_DetermineInterfaceStatus(name.String())];
437 
438 		BMessage* info = new BMessage(kMsgShowConfiguration);
439 		info->AddString("interface", name.String());
440 		menu->AddItem(new BMenuItem(label.String(), info));
441 
442 		// We only show the networks of the first wireless device we find.
443 		if (wifiInterface.IsEmpty()) {
444 			wifiDevice.SetTo(name);
445 			if (wifiDevice.IsWireless())
446 				wifiInterface = name;
447 		}
448 	}
449 
450 	if (!fInterfaceStatuses.empty())
451 		menu->AddSeparatorItem();
452 
453 	// Add wireless networks, if any
454 
455 	if (!wifiInterface.IsEmpty()) {
456 		std::set<BNetworkAddress> associated;
457 		BNetworkAddress address;
458 		uint32 cookie = 0;
459 		while (wifiDevice.GetNextAssociatedNetwork(cookie, address) == B_OK)
460 			associated.insert(address);
461 
462 		wireless_network network;
463 		int32 count = 0;
464 		cookie = 0;
465 		while (wifiDevice.GetNextNetwork(cookie, network) == B_OK) {
466 			BMessage* message = new BMessage(kMsgJoinNetwork);
467 			message->AddString("device", wifiInterface);
468 			message->AddString("name", network.name);
469 
470 			BMenuItem* item = new WirelessNetworkMenuItem(network.name,
471 				network.signal_strength,
472 				(network.flags & B_NETWORK_IS_ENCRYPTED) != 0, message);
473 			menu->AddItem(item);
474 			if (associated.find(network.address) != associated.end())
475 				item->SetMarked(true);
476 
477 			count++;
478 		}
479 		if (count == 0) {
480 			BMenuItem* item = new BMenuItem(
481 				B_TRANSLATE("<no wireless networks found>"), NULL);
482 			item->SetEnabled(false);
483 			menu->AddItem(item);
484 		}
485 		menu->AddSeparatorItem();
486 	}
487 
488 	menu->AddItem(new BMenuItem(B_TRANSLATE(
489 		"Open network preferences" B_UTF8_ELLIPSIS),
490 		new BMessage(kMsgOpenNetworkPreferences)));
491 
492 	if (fInDeskbar) {
493 		menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
494 			new BMessage(B_QUIT_REQUESTED)));
495 	}
496 	menu->SetTargetForItems(this);
497 
498 	ConvertToScreen(&point);
499 	menu->Go(point, true, true, true);
500 }
501 
502 
503 void
504 NetworkStatusView::_AboutRequested()
505 {
506 	BString about = B_TRANSLATE(
507 		"NetworkStatus\n\twritten by %1 and Hugo Santos\n\t%2, Haiku, Inc.\n"
508 		);
509 	about.ReplaceFirst("%1", "Axel Dörfler");
510 		// Append a new developer here
511 	about.ReplaceFirst("%2", "Copyright 2007-2010");
512 		// Append a new year here
513 	BAlert* alert = new BAlert("about", about, B_TRANSLATE("OK"));
514 	alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
515 	BTextView *view = alert->TextView();
516 	BFont font;
517 
518 	view->SetStylable(true);
519 
520 	view->GetFont(&font);
521 	font.SetSize(18);
522 	font.SetFace(B_BOLD_FACE);
523 	view->SetFontAndColor(0, 13, &font);
524 
525 	alert->Go();
526 }
527 
528 
529 bool
530 NetworkStatusView::_PrepareRequest(struct ifreq& request, const char* name)
531 {
532 	if (strlen(name) > IF_NAMESIZE)
533 		return false;
534 
535 	strcpy(request.ifr_name, name);
536 	return true;
537 }
538 
539 
540 int32
541 NetworkStatusView::_DetermineInterfaceStatus(
542 	const BNetworkInterface& interface)
543 {
544 	uint32 flags = interface.Flags();
545 	int32 status = kStatusNoLink;
546 
547 	// TODO: no kStatusLinkNoConfig yet
548 
549 	if (flags & IFF_CONFIGURING)
550 		status = kStatusConnecting;
551 	else if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK))
552 		status = kStatusReady;
553 
554 	return status;
555 }
556 
557 
558 void
559 NetworkStatusView::_Update(bool force)
560 {
561 	BNetworkRoster& roster = BNetworkRoster::Default();
562 	BNetworkInterface interface;
563 	uint32 cookie = 0;
564 
565 	while (roster.GetNextInterface(&cookie, interface) == B_OK) {
566 		if ((interface.Flags() & IFF_LOOPBACK) == 0) {
567 			int32 oldStatus = kStatusUnknown;
568 			if (fInterfaceStatuses.find(interface.Name())
569 				!= fInterfaceStatuses.end()) {
570 				oldStatus = fInterfaceStatuses[interface.Name()];
571 			}
572 			int32 status = _DetermineInterfaceStatus(interface);
573 			if (oldStatus != status) {
574 				BNotification notification(B_INFORMATION_NOTIFICATION);
575 				notification.SetGroup(B_TRANSLATE("Network Status"));
576 				notification.SetTitle(interface.Name());
577 				notification.SetMessageID(interface.Name());
578 				notification.SetIcon(fNotifyIcons[status]);
579 				if (status == kStatusConnecting
580 					|| (status == kStatusReady
581 						&& oldStatus == kStatusConnecting)
582 					|| (status == kStatusNoLink
583 						&& oldStatus == kStatusReady)
584 					|| (status == kStatusNoLink
585 						&& oldStatus == kStatusConnecting)) {
586 					// A significant state change, raise notification.
587 					notification.SetContent(kStatusDescriptions[status]);
588 					notification.Send();
589 				}
590 				Invalidate();
591 			}
592 			fInterfaceStatuses[interface.Name()] = status;
593 		}
594 	}
595 }
596 
597 
598 void
599 NetworkStatusView::_OpenNetworksPreferences()
600 {
601 	status_t status = be_roster->Launch("application/x-vnd.Haiku-Network");
602 	if (status != B_OK && status != B_ALREADY_RUNNING) {
603 		BString errorMessage(B_TRANSLATE("Launching the network preflet "
604 			"failed.\n\nError: "));
605 		errorMessage << strerror(status);
606 		BAlert* alert = new BAlert("launch error", errorMessage.String(),
607 			B_TRANSLATE("OK"));
608 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
609 
610 		// asynchronous alert in order to not block replicant host application
611 		alert->Go(NULL);
612 	}
613 }
614 
615 
616 //	#pragma mark -
617 
618 
619 extern "C" _EXPORT BView *
620 instantiate_deskbar_item(void)
621 {
622 	return new NetworkStatusView(BRect(0, 0, 15, 15),
623 		B_FOLLOW_LEFT | B_FOLLOW_TOP, true);
624 }
625 
626