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