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