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