xref: /haiku/src/apps/networkstatus/NetworkStatusView.cpp (revision 58481f0f6ef1a61ba07283f012cafbc2ed874ead)
1 /*
2  * Copyright 2006-2007, 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 <arpa/inet.h>
15 #include <net/if.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/socket.h>
20 #include <sys/sockio.h>
21 #include <unistd.h>
22 
23 #include <Alert.h>
24 #include <Application.h>
25 #include <Bitmap.h>
26 #include <Deskbar.h>
27 #include <Dragger.h>
28 #include <Drivers.h>
29 #include <IconUtils.h>
30 #include <MenuItem.h>
31 #include <MessageRunner.h>
32 #include <PopUpMenu.h>
33 #include <Resources.h>
34 #include <Roster.h>
35 #include <String.h>
36 #include <TextView.h>
37 
38 #include <net_notifications.h>
39 
40 #include "NetworkStatus.h"
41 #include "NetworkStatusIcons.h"
42 
43 
44 static const char *kStatusDescriptions[] = {
45 	"Unknown",
46 	"No Link",
47 	"No stateful configuration",
48 	"Configuring",
49 	"Ready"
50 };
51 
52 extern "C" _EXPORT BView *instantiate_deskbar_item(void);
53 
54 
55 const uint32 kMsgShowConfiguration = 'shcf';
56 const uint32 kMsgOpenNetworkPreferences = 'onwp';
57 
58 const uint32 kMinIconWidth = 16;
59 const uint32 kMinIconHeight = 16;
60 
61 
62 class SocketOpener {
63 public:
64 	SocketOpener()
65 	{
66 		fSocket = socket(AF_INET, SOCK_DGRAM, 0);
67 	}
68 
69 	~SocketOpener()
70 	{
71 		close(fSocket);
72 	}
73 
74 	status_t InitCheck()
75 	{
76 		return fSocket >= 0 ? B_OK : B_ERROR;
77 	}
78 
79 	operator int() const
80 	{
81 		return fSocket;
82 	}
83 
84 private:
85 	int	fSocket;
86 };
87 
88 
89 //	#pragma mark -
90 
91 
92 NetworkStatusView::NetworkStatusView(BRect frame, int32 resizingMode,
93 		bool inDeskbar)
94 	: BView(frame, kDeskbarItemName, resizingMode,
95 		B_WILL_DRAW | B_FRAME_EVENTS),
96 	fInDeskbar(inDeskbar),
97 	fStatus(kStatusUnknown)
98 {
99 	_Init();
100 
101 	if (!inDeskbar) {
102 		// we were obviously added to a standard window - let's add a dragger
103 		frame.OffsetTo(B_ORIGIN);
104 		frame.top = frame.bottom - 7;
105 		frame.left = frame.right - 7;
106 		BDragger* dragger = new BDragger(frame, this,
107 			B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
108 		AddChild(dragger);
109 	} else
110 		_Update();
111 }
112 
113 
114 NetworkStatusView::NetworkStatusView(BMessage* archive)
115 	: BView(archive),
116 	fInDeskbar(false)
117 {
118 	app_info info;
119 	if (be_app->GetAppInfo(&info) == B_OK
120 		&& !strcasecmp(info.signature, "application/x-vnd.Be-TSKB"))
121 		fInDeskbar = true;
122 
123 	_Init();
124 }
125 
126 
127 NetworkStatusView::~NetworkStatusView()
128 {
129 }
130 
131 
132 void
133 NetworkStatusView::_Init()
134 {
135 	for (int i = 0; i < kStatusCount; i++) {
136 		fBitmaps[i] = NULL;
137 	}
138 
139 	_UpdateBitmaps();
140 }
141 
142 
143 void
144 NetworkStatusView::_UpdateBitmaps()
145 {
146 	for (int i = 0; i < kStatusCount; i++) {
147 		delete fBitmaps[i];
148 		fBitmaps[i] = NULL;
149 	}
150 
151 	image_info info;
152 	if (our_image(info) != B_OK)
153 		return;
154 
155 	BFile file(info.name, B_READ_ONLY);
156 	if (file.InitCheck() < B_OK)
157 		return;
158 
159 	BResources resources(&file);
160 #ifdef HAIKU_TARGET_PLATFORM_HAIKU
161 	if (resources.InitCheck() < B_OK)
162 		return;
163 #endif
164 
165 	for (int i = 0; i < kStatusCount; i++) {
166 		const void* data = NULL;
167 		size_t size;
168 		data = resources.LoadResource(B_VECTOR_ICON_TYPE,
169 			kNetworkStatusNoDevice + i, &size);
170 		if (data != NULL) {
171 			BBitmap* icon = new BBitmap(Bounds(), B_RGBA32);
172 			if (icon->InitCheck() == B_OK
173 				&& BIconUtils::GetVectorIcon((const uint8 *)data,
174 					size, icon) == B_OK) {
175 				fBitmaps[i] = icon;
176 			} else
177 				delete icon;
178 		}
179 	}
180 }
181 
182 
183 void
184 NetworkStatusView::_Quit()
185 {
186 	if (fInDeskbar) {
187 		BDeskbar deskbar;
188 		deskbar.RemoveItem(kDeskbarItemName);
189 	} else
190 		be_app->PostMessage(B_QUIT_REQUESTED);
191 }
192 
193 
194 NetworkStatusView *
195 NetworkStatusView::Instantiate(BMessage* archive)
196 {
197 	if (!validate_instantiation(archive, "NetworkStatusView"))
198 		return NULL;
199 
200 	return new NetworkStatusView(archive);
201 }
202 
203 
204 status_t
205 NetworkStatusView::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 
217 void
218 NetworkStatusView::AttachedToWindow()
219 {
220 	BView::AttachedToWindow();
221 	if (Parent())
222 		SetViewColor(Parent()->ViewColor());
223 	else
224 		SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
225 
226 	SetLowColor(ViewColor());
227 
228 	start_watching_network(
229 		B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this);
230 
231 	_Update();
232 }
233 
234 
235 void
236 NetworkStatusView::DetachedFromWindow()
237 {
238 	stop_watching_network(this);
239 }
240 
241 
242 void
243 NetworkStatusView::MessageReceived(BMessage* message)
244 {
245 	switch (message->what) {
246 		case B_NETWORK_MONITOR:
247 			_Update();
248 			break;
249 
250 		case kMsgShowConfiguration:
251 			_ShowConfiguration(message);
252 			break;
253 
254 		case kMsgOpenNetworkPreferences:
255 			_OpenNetworksPreferences();
256 			break;
257 
258 		case B_ABOUT_REQUESTED:
259 			_AboutRequested();
260 			break;
261 
262 		case B_QUIT_REQUESTED:
263 			_Quit();
264 			break;
265 
266 		default:
267 			BView::MessageReceived(message);
268 	}
269 }
270 
271 
272 void
273 NetworkStatusView::FrameResized(float width, float height)
274 {
275 	_UpdateBitmaps();
276 	Invalidate();
277 }
278 
279 
280 void
281 NetworkStatusView::Draw(BRect updateRect)
282 {
283 	if (fBitmaps[fStatus] == NULL)
284 		return;
285 
286 	SetDrawingMode(B_OP_ALPHA);
287 	DrawBitmap(fBitmaps[fStatus]);
288 	SetDrawingMode(B_OP_COPY);
289 }
290 
291 
292 void
293 NetworkStatusView::_ShowConfiguration(BMessage* message)
294 {
295 	static const struct information_entry {
296 		const char*	label;
297 		int32		control;
298 	} kInformationEntries[] = {
299 		{ "Address", SIOCGIFADDR },
300 		{ "Broadcast", SIOCGIFBRDADDR },
301 		{ "Netmask", SIOCGIFNETMASK },
302 		{ NULL }
303 	};
304 
305 	SocketOpener socket;
306 	if (socket.InitCheck() != B_OK)
307 		return;
308 
309 	const char* name;
310 	if (message->FindString("interface", &name) != B_OK)
311 		return;
312 
313 	ifreq request;
314 	if (!_PrepareRequest(request, name))
315 		return;
316 
317 	BString text = name;
318 	text += " information:\n";
319 	size_t boldLength = text.Length();
320 
321 	for (int i = 0; kInformationEntries[i].label; i++) {
322 		if (ioctl(socket, kInformationEntries[i].control, &request,
323 				sizeof(request)) < 0) {
324 			continue;
325 		}
326 
327 		char address[32];
328 		sockaddr_in* inetAddress = NULL;
329 		switch (kInformationEntries[i].control) {
330 			case SIOCGIFNETMASK:
331 				inetAddress = (sockaddr_in*)&request.ifr_mask;
332 				break;
333 			default:
334 				inetAddress = (sockaddr_in*)&request.ifr_addr;
335 				break;
336 		}
337 
338 		if (inet_ntop(AF_INET, &inetAddress->sin_addr, address,
339 				sizeof(address)) == NULL) {
340 			return;
341 		}
342 
343 		text += "\n";
344 		text += kInformationEntries[i].label;
345 		text += ": ";
346 		text += address;
347 	}
348 
349 	BAlert* alert = new BAlert(name, text.String(), "Ok");
350 	BTextView* view = alert->TextView();
351 	BFont font;
352 
353 	view->SetStylable(true);
354 	view->GetFont(&font);
355 	font.SetFace(B_BOLD_FACE);
356 	view->SetFontAndColor(0, boldLength, &font);
357 
358 	alert->Go(NULL);
359 }
360 
361 
362 void
363 NetworkStatusView::MouseDown(BPoint point)
364 {
365 	BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
366 	menu->SetAsyncAutoDestruct(true);
367 	menu->SetFont(be_plain_font);
368 
369 	for (int32 i = 0; i < fInterfaces.CountItems(); i++) {
370 		BString& name = *fInterfaces.ItemAt(i);
371 
372 		BString label = name;
373 		label += ": ";
374 		label += kStatusDescriptions[
375 			_DetermineInterfaceStatus(name.String())];
376 
377 		BMessage* info = new BMessage(kMsgShowConfiguration);
378 		info->AddString("interface", name.String());
379 		menu->AddItem(new BMenuItem(label.String(), info));
380 	}
381 
382 	menu->AddSeparatorItem();
383 	//menu->AddItem(new BMenuItem("About NetworkStatus" B_UTF8_ELLIPSIS,
384 	//	new BMessage(B_ABOUT_REQUESTED)));
385 	menu->AddItem(new BMenuItem("Open Networks Preferences" B_UTF8_ELLIPSIS,
386 		new BMessage(kMsgOpenNetworkPreferences)));
387 
388 	if (fInDeskbar)
389 		menu->AddItem(new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED)));
390 	menu->SetTargetForItems(this);
391 
392 	ConvertToScreen(&point);
393 	menu->Go(point, true, true, true);
394 }
395 
396 
397 void
398 NetworkStatusView::_AboutRequested()
399 {
400 	BAlert* alert = new BAlert("about", "NetworkStatus\n"
401 		"\twritten by Axel Dörfler and Hugo Santos\n"
402 		"\tCopyright 2007, Haiku, Inc.\n", "Ok");
403 	BTextView *view = alert->TextView();
404 	BFont font;
405 
406 	view->SetStylable(true);
407 
408 	view->GetFont(&font);
409 	font.SetSize(18);
410 	font.SetFace(B_BOLD_FACE);
411 	view->SetFontAndColor(0, 13, &font);
412 
413 	alert->Go();
414 }
415 
416 
417 bool
418 NetworkStatusView::_PrepareRequest(struct ifreq& request, const char* name)
419 {
420 	if (strlen(name) > IF_NAMESIZE)
421 		return false;
422 
423 	strcpy(request.ifr_name, name);
424 	return true;
425 }
426 
427 
428 int32
429 NetworkStatusView::_DetermineInterfaceStatus(const char* name)
430 {
431 	SocketOpener socket;
432 	if (socket.InitCheck() != B_OK)
433 		return kStatusUnknown;
434 
435 	ifreq request;
436 	if (!_PrepareRequest(request, name))
437 		return kStatusUnknown;
438 
439 	uint32 flags = 0;
440 	if (ioctl(socket, SIOCGIFFLAGS, &request, sizeof(struct ifreq)) == 0)
441 		flags = request.ifr_flags;
442 
443 	int32 status = kStatusNoLink;
444 
445 	// TODO: no kStatusLinkNoConfig yet
446 
447 	if (flags & IFF_CONFIGURING)
448 		status = kStatusConnecting;
449 	else if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK))
450 		status = kStatusReady;
451 
452 	return status;
453 }
454 
455 
456 void
457 NetworkStatusView::_Update(bool force)
458 {
459 	SocketOpener socket;
460 	if (socket.InitCheck() != B_OK)
461 		return;
462 
463 	// iterate over all interfaces and retrieve minimal status
464 
465 	ifconf config;
466 	config.ifc_len = sizeof(config.ifc_value);
467 	if (ioctl(socket, SIOCGIFCOUNT, &config, sizeof(struct ifconf)) < 0)
468 		return;
469 
470 	uint32 count = (uint32)config.ifc_value;
471 	if (count == 0)
472 		return;
473 
474 	void* buffer = malloc(count * sizeof(struct ifreq));
475 	if (buffer == NULL)
476 		return;
477 
478 	config.ifc_len = count * sizeof(struct ifreq);
479 	config.ifc_buf = buffer;
480 	if (ioctl(socket, SIOCGIFCONF, &config, sizeof(struct ifconf)) < 0) {
481 		free(buffer);
482 		return;
483 	}
484 
485 	ifreq* interface = (ifreq*)buffer;
486 
487 	int32 oldStatus = fStatus;
488 	fStatus = kStatusUnknown;
489 	fInterfaces.MakeEmpty();
490 
491 	for (uint32 i = 0; i < count; i++) {
492 		if (strncmp(interface->ifr_name, "loop", 4) && interface->ifr_name[0]) {
493 			fInterfaces.AddItem(new BString(interface->ifr_name));
494 			int32 status = _DetermineInterfaceStatus(interface->ifr_name);
495 			if (status > fStatus)
496 				fStatus = status;
497 		}
498 
499 		interface = (ifreq *)((addr_t)interface + IF_NAMESIZE
500 			+ interface->ifr_addr.sa_len);
501 	}
502 
503 	free(buffer);
504 
505 	if (fStatus != oldStatus)
506 		Invalidate();
507 }
508 
509 
510 void
511 NetworkStatusView::_OpenNetworksPreferences()
512 {
513 	status_t status = be_roster->Launch("application/x-vnd.Haiku-Network");
514 	if (status < B_OK) {
515 		BString errorMessage("Launching the Network preflet failed.\n\n"
516 			"Error: ");
517 		errorMessage << strerror(status);
518 		BAlert* alert = new BAlert("launch error", errorMessage.String(),
519 			"Ok");
520 
521 		// asynchronous alert in order to not block replicant host application
522 		alert->Go(NULL);
523 	}
524 }
525 
526 
527 //	#pragma mark -
528 
529 
530 extern "C" _EXPORT BView *
531 instantiate_deskbar_item(void)
532 {
533 	return new NetworkStatusView(BRect(0, 0, 15, 15),
534 		B_FOLLOW_LEFT | B_FOLLOW_TOP, true);
535 }
536 
537