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