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
58static 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
66extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth, float maxHeight);
67
68
69const uint32 kMsgShowConfiguration = 'shcf';
70const uint32 kMsgOpenNetworkPreferences = 'onwp';
71const uint32 kMsgJoinNetwork = 'join';
72
73const uint32 kMinIconWidth = 16;
74const uint32 kMinIconHeight = 16;
75
76
77//	#pragma mark - NetworkStatusView
78
79
80NetworkStatusView::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
101NetworkStatusView::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
114NetworkStatusView::~NetworkStatusView()
115{
116}
117
118
119void
120NetworkStatusView::_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
131void
132NetworkStatusView::_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
183void
184NetworkStatusView::_Quit()
185{
186	if (fInDeskbar) {
187		BDeskbar deskbar;
188		deskbar.RemoveItem(kDeskbarItemName);
189	} else
190		be_app->PostMessage(B_QUIT_REQUESTED);
191}
192
193
194NetworkStatusView*
195NetworkStatusView::Instantiate(BMessage* archive)
196{
197	if (!validate_instantiation(archive, "NetworkStatusView"))
198		return NULL;
199
200	return new NetworkStatusView(archive);
201}
202
203
204status_t
205NetworkStatusView::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
217void
218NetworkStatusView::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
231void
232NetworkStatusView::DetachedFromWindow()
233{
234	stop_watching_network(this);
235}
236
237
238void
239NetworkStatusView::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
293void
294NetworkStatusView::FrameResized(float width, float height)
295{
296	_UpdateBitmaps();
297	Invalidate();
298}
299
300
301void
302NetworkStatusView::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
320void
321NetworkStatusView::_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
375void
376NetworkStatusView::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
479void
480NetworkStatusView::_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
498int32
499NetworkStatusView::_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
517void
518NetworkStatusView::_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
570void
571NetworkStatusView::_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
591extern "C" _EXPORT BView *
592instantiate_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