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