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