xref: /haiku/src/preferences/network/NetworkWindow.cpp (revision d0b107f3110baf1799df4c488c64848a1e232d0a)
1 /*
2  * Copyright 2004-2015 Haiku Inc. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  *	Authors:
6  *		Adrien Destugues, <pulkomandy@pulkomandy.tk>
7  *		Axel Dörfler, <axeld@pinc-software.de>
8  *		Alexander von Gluck, <kallisti5@unixzen.com>
9  */
10 
11 
12 #include "NetworkWindow.h"
13 
14 #include <net/if.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 
19 #include <Alert.h>
20 #include <Application.h>
21 #include <Button.h>
22 #include <Catalog.h>
23 #include <CheckBox.h>
24 #include <ControlLook.h>
25 #include <Deskbar.h>
26 #include <Directory.h>
27 #include <LayoutBuilder.h>
28 #include <NetworkInterface.h>
29 #include <NetworkNotifications.h>
30 #include <NetworkRoster.h>
31 #include <OutlineListView.h>
32 #include <Path.h>
33 #include <PathFinder.h>
34 #include <PathMonitor.h>
35 #include <Roster.h>
36 #include <ScrollView.h>
37 #include <StringItem.h>
38 #include <SymLink.h>
39 
40 #define ENABLE_PROFILES 0
41 #if ENABLE_PROFILES
42 #	include <PopUpMenu.h>
43 #endif
44 
45 #include "InterfaceListItem.h"
46 #include "InterfaceView.h"
47 
48 
49 const char* kNetworkStatusSignature = "application/x-vnd.Haiku-NetworkStatus";
50 
51 static const uint32 kMsgProfileSelected = 'prof';
52 static const uint32 kMsgProfileManage = 'mngp';
53 static const uint32 kMsgProfileNew = 'newp';
54 static const uint32 kMsgRevert = 'rvrt';
55 static const uint32 kMsgToggleReplicant = 'trep';
56 static const uint32 kMsgItemSelected = 'ItSl';
57 
58 
59 #undef B_TRANSLATION_CONTEXT
60 #define B_TRANSLATION_CONTEXT	"NetworkWindow"
61 
62 
63 class TitleItem : public BStringItem {
64 public:
65 	TitleItem(const char* title)
66 		:
67 		BStringItem(title)
68 	{
69 	}
70 
71 	void DrawItem(BView* owner, BRect bounds, bool complete)
72 	{
73 		owner->SetFont(be_bold_font);
74 		BStringItem::DrawItem(owner, bounds, complete);
75 		owner->SetFont(be_plain_font);
76 	}
77 
78 	void Update(BView* owner, const BFont* font)
79 	{
80 		BStringItem::Update(owner, be_bold_font);
81 	}
82 };
83 
84 
85 // #pragma mark -
86 
87 
88 NetworkWindow::NetworkWindow()
89 	:
90 	BWindow(BRect(100, 100, 400, 400), B_TRANSLATE("Network"), B_TITLED_WINDOW,
91 		B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS),
92 	fServicesItem(NULL),
93 	fDialUpItem(NULL),
94 	fOtherItem(NULL)
95 {
96 	// Profiles section
97 #if ENABLE_PROFILES
98 	BPopUpMenu* profilesPopup = new BPopUpMenu("<none>");
99 	_BuildProfilesMenu(profilesPopup, kMsgProfileSelected);
100 
101 	BMenuField* profilesMenuField = new BMenuField("profiles_menu",
102 		B_TRANSLATE("Profile:"), profilesPopup);
103 
104 	profilesMenuField->SetFont(be_bold_font);
105 	profilesMenuField->SetEnabled(false);
106 #endif
107 
108 	// Settings section
109 
110 	fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
111 		new BMessage(kMsgRevert));
112 	// fRevertButton->SetEnabled(false);
113 
114 	BMessage* message = new BMessage(kMsgToggleReplicant);
115 	BCheckBox* showReplicantCheckBox = new BCheckBox("showReplicantCheckBox",
116 		B_TRANSLATE("Show network status in Deskbar"), message);
117 	showReplicantCheckBox->SetExplicitMaxSize(
118 		BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
119 	showReplicantCheckBox->SetValue(_IsReplicantInstalled());
120 
121 	fListView = new BOutlineListView("list", B_SINGLE_SELECTION_LIST,
122 		B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS | B_NAVIGABLE);
123 	fListView->SetSelectionMessage(new BMessage(kMsgItemSelected));
124 
125 	BScrollView* scrollView = new BScrollView("ScrollView", fListView,
126 		0, false, true);
127 
128 	fAddOnShellView = new BView("add-on shell", 0,
129 		new BGroupLayout(B_VERTICAL));
130 	fAddOnShellView->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));
131 
132 	fInterfaceView = new InterfaceView();
133 
134 	// Build the layout
135 	BLayoutBuilder::Group<>(this, B_VERTICAL)
136 		.SetInsets(B_USE_DEFAULT_SPACING)
137 
138 #if ENABLE_PROFILES
139 		.AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
140 			.Add(profilesMenuField)
141 			.AddGlue()
142 		.End()
143 #endif
144 		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
145 			.Add(scrollView)
146 			.Add(fAddOnShellView)
147 			.End()
148 		.Add(showReplicantCheckBox)
149 		.AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
150 			.Add(fRevertButton)
151 			.AddGlue()
152 		.End();
153 
154 	_ScanInterfaces();
155 	_ScanAddOns();
156 
157 	// Set size of the list view from its contents
158 	float width;
159 	float height;
160 	fListView->GetPreferredSize(&width, &height);
161 	width += 2 * be_control_look->DefaultItemSpacing();
162 	fListView->SetExplicitSize(BSize(width, B_SIZE_UNSET));
163 	fListView->SetExplicitMinSize(BSize(width, std::min(height, 400.f)));
164 
165 	CenterOnScreen();
166 
167 	fSettings.StartMonitoring(this);
168 	start_watching_network(B_WATCH_NETWORK_INTERFACE_CHANGES
169 		| B_WATCH_NETWORK_LINK_CHANGES | B_WATCH_NETWORK_WLAN_CHANGES, this);
170 }
171 
172 
173 NetworkWindow::~NetworkWindow()
174 {
175 	stop_watching_network(this);
176 	fSettings.StopMonitoring(this);
177 }
178 
179 
180 bool
181 NetworkWindow::QuitRequested()
182 {
183 	be_app->PostMessage(B_QUIT_REQUESTED);
184 	return true;
185 }
186 
187 
188 void
189 NetworkWindow::MessageReceived(BMessage* message)
190 {
191 	switch (message->what) {
192 		case kMsgProfileNew:
193 			break;
194 
195 		case kMsgProfileSelected:
196 		{
197 			const char* path;
198 			if (message->FindString("path", &path) != B_OK)
199 				break;
200 
201 			// TODO!
202 			break;
203 		}
204 
205 		case kMsgItemSelected:
206 		{
207 			BListItem* listItem = fListView->FullListItemAt(
208 				fListView->FullListCurrentSelection());
209 			if (listItem == NULL)
210 				break;
211 
212 			_SelectItem(listItem);
213 			break;
214 		}
215 
216 		case kMsgRevert:
217 		{
218 			SettingsMap::const_iterator iterator = fSettingsMap.begin();
219 			for (; iterator != fSettingsMap.end(); iterator++)
220 				iterator->second->Revert();
221 			break;
222 		}
223 
224 		case kMsgToggleReplicant:
225 		{
226 			_ShowReplicant(
227 				message->GetInt32("be:value", B_CONTROL_OFF) == B_CONTROL_ON);
228 			break;
229 		}
230 
231 		case B_PATH_MONITOR:
232 		{
233 			fSettings.Update(message);
234 			break;
235 		}
236 
237 		case B_NETWORK_MONITOR:
238 			_BroadcastConfigurationUpdate(*message);
239 			break;
240 
241 		case BNetworkSettings::kMsgInterfaceSettingsUpdated:
242 		case BNetworkSettings::kMsgNetworkSettingsUpdated:
243 		case BNetworkSettings::kMsgServiceSettingsUpdated:
244 			_BroadcastSettingsUpdate(message->what);
245 			break;
246 
247 		default:
248 			inherited::MessageReceived(message);
249 	}
250 }
251 
252 
253 void
254 NetworkWindow::_BuildProfilesMenu(BMenu* menu, int32 what)
255 {
256 	char currentProfile[256] = { 0 };
257 
258 	menu->SetRadioMode(true);
259 
260 	BDirectory dir("/boot/system/settings/network/profiles");
261 	if (dir.InitCheck() == B_OK) {
262 		BEntry entry;
263 
264 		dir.Rewind();
265 		while (dir.GetNextEntry(&entry) >= 0) {
266 			BPath name;
267 			entry.GetPath(&name);
268 
269 			if (entry.IsSymLink() &&
270 				strcmp("current", name.Leaf()) == 0) {
271 				BSymLink symlink(&entry);
272 
273 				if (symlink.IsAbsolute())
274 					// oh oh, sorry, wrong symlink...
275 					continue;
276 
277 				symlink.ReadLink(currentProfile, sizeof(currentProfile));
278 				continue;
279 			};
280 
281 			if (!entry.IsDirectory())
282 				continue;
283 
284 			BMessage* message = new BMessage(what);
285 			message->AddString("path", name.Path());
286 
287 			BMenuItem* item = new BMenuItem(name.Leaf(), message);
288 			menu->AddItem(item);
289 		}
290 	}
291 
292 	menu->AddSeparatorItem();
293 	menu->AddItem(new BMenuItem(B_TRANSLATE("New" B_UTF8_ELLIPSIS),
294 		new BMessage(kMsgProfileNew)));
295 	menu->AddItem(new BMenuItem(B_TRANSLATE("Manage" B_UTF8_ELLIPSIS),
296 		new BMessage(kMsgProfileManage)));
297 
298 	if (currentProfile[0] != '\0') {
299 		BMenuItem* item = menu->FindItem(currentProfile);
300 		if (item != NULL) {
301 			// TODO: translate
302 			BString label(item->Label());
303 			label << " (current)";
304 			item->SetLabel(label.String());
305 			item->SetMarked(true);
306 		}
307 	}
308 }
309 
310 
311 void
312 NetworkWindow::_ScanInterfaces()
313 {
314 	// Try existing devices first
315 	BNetworkRoster& roster = BNetworkRoster::Default();
316 	BNetworkInterface interface;
317 	uint32 cookie = 0;
318 
319 	while (roster.GetNextInterface(&cookie, interface) == B_OK) {
320 		if ((interface.Flags() & IFF_LOOPBACK) != 0)
321 			continue;
322 
323 		InterfaceListItem* item = new InterfaceListItem(interface.Name());
324 		item->SetExpanded(true);
325 
326 		fInterfaceItemMap.insert(std::pair<BString, InterfaceListItem*>(
327 			BString(interface.Name()), item));
328 		fListView->AddItem(item);
329 	}
330 
331 	// TODO: Then consider those from the settings (for example, for USB)
332 }
333 
334 
335 void
336 NetworkWindow::_ScanAddOns()
337 {
338 	BStringList paths;
339 	BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Network Settings",
340 		paths);
341 
342 	for (int32 i = 0; i < paths.CountStrings(); i++) {
343 		BDirectory directory(paths.StringAt(i));
344 		BEntry entry;
345 		while (directory.GetNextEntry(&entry) == B_OK) {
346 			BPath path;
347 			if (entry.GetPath(&path) != B_OK)
348 				continue;
349 
350 			image_id image = load_add_on(path.Path());
351 			if (image < 0) {
352 				printf("Failed to load %s addon: %s.\n", path.Path(),
353 					strerror(image));
354 				continue;
355 			}
356 
357 			BNetworkSettingsAddOn* (*instantiateAddOn)(image_id image,
358 				BNetworkSettings& settings);
359 
360 			status_t status = get_image_symbol(image,
361 				"instantiate_network_settings_add_on",
362 				B_SYMBOL_TYPE_TEXT, (void**)&instantiateAddOn);
363 			if (status != B_OK) {
364 				// No "addon instantiate function" symbol found in this addon
365 				printf("No symbol \"instantiate_network_settings_add_on\" "
366 					"found in %s addon: not a network setup addon!\n",
367 					path.Path());
368 				unload_add_on(image);
369 				continue;
370 			}
371 
372 			BNetworkSettingsAddOn* addOn = instantiateAddOn(image, fSettings);
373 			if (addOn == NULL) {
374 				unload_add_on(image);
375 				continue;
376 			}
377 
378 			fAddOns.AddItem(addOn);
379 
380 			// Per interface items
381 			ItemMap::const_iterator iterator = fInterfaceItemMap.begin();
382 			for (; iterator != fInterfaceItemMap.end(); iterator++) {
383 				const BString& interface = iterator->first;
384 				BListItem* interfaceItem = iterator->second;
385 
386 				uint32 cookie = 0;
387 				while (true) {
388 					BNetworkSettingsItem* item = addOn->CreateNextInterfaceItem(
389 						cookie, interface.String());
390 					if (item == NULL)
391 						break;
392 
393 					fSettingsMap[item->ListItem()] = item;
394 					fListView->AddUnder(item->ListItem(), interfaceItem);
395 				}
396 				fListView->SortItemsUnder(interfaceItem, true,
397 					NetworkWindow::_CompareListItems);
398 			}
399 
400 			// Generic items
401 			uint32 cookie = 0;
402 			while (true) {
403 				BNetworkSettingsItem* item = addOn->CreateNextItem(cookie);
404 				if (item == NULL)
405 					break;
406 
407 				fSettingsMap[item->ListItem()] = item;
408 				fListView->AddUnder(item->ListItem(),
409 					_ListItemFor(item->Type()));
410 			}
411 
412 			_SortItemsUnder(fServicesItem);
413 			_SortItemsUnder(fDialUpItem);
414 			_SortItemsUnder(fOtherItem);
415 		}
416 	}
417 
418 	fListView->SortItemsUnder(NULL, true,
419 		NetworkWindow::_CompareTopLevelListItems);
420 }
421 
422 
423 BNetworkSettingsItem*
424 NetworkWindow::_SettingsItemFor(BListItem* item)
425 {
426 	SettingsMap::const_iterator found = fSettingsMap.find(item);
427 	if (found != fSettingsMap.end())
428 		return found->second;
429 
430 	return NULL;
431 }
432 
433 
434 void
435 NetworkWindow::_SortItemsUnder(BListItem* item)
436 {
437 	if (item != NULL)
438 		fListView->SortItemsUnder(item, true, NetworkWindow::_CompareListItems);
439 }
440 
441 
442 BListItem*
443 NetworkWindow::_ListItemFor(BNetworkSettingsType type)
444 {
445 	switch (type) {
446 		case B_NETWORK_SETTINGS_TYPE_SERVICE:
447 			if (fServicesItem == NULL)
448 				fServicesItem = _CreateItem(B_TRANSLATE("Services"));
449 			return fServicesItem;
450 
451 		case B_NETWORK_SETTINGS_TYPE_DIAL_UP:
452 			if (fDialUpItem == NULL)
453 				fDialUpItem = _CreateItem(B_TRANSLATE("Dial Up"));
454 			return fDialUpItem;
455 
456 		case B_NETWORK_SETTINGS_TYPE_OTHER:
457 			if (fOtherItem == NULL)
458 				fOtherItem = _CreateItem(B_TRANSLATE("Other"));
459 			return fOtherItem;
460 
461 		default:
462 			return NULL;
463 	}
464 }
465 
466 
467 BListItem*
468 NetworkWindow::_CreateItem(const char* label)
469 {
470 	BListItem* item = new TitleItem(label);
471 	item->SetExpanded(true);
472 	fListView->AddItem(item);
473 	return item;
474 }
475 
476 
477 void
478 NetworkWindow::_SelectItem(BListItem* listItem)
479 {
480 	while (fAddOnShellView->CountChildren() > 0)
481 		fAddOnShellView->ChildAt(0)->RemoveSelf();
482 
483 	BView* nextView = NULL;
484 
485 	BNetworkSettingsItem* item = _SettingsItemFor(listItem);
486 	if (item != NULL) {
487 		nextView = item->View();
488 	} else {
489 		InterfaceListItem* item = dynamic_cast<InterfaceListItem*>(
490 			listItem);
491 		if (item != NULL) {
492 			fInterfaceView->SetTo(item->Name());
493 			nextView = fInterfaceView;
494 		}
495 	}
496 
497 	if (nextView != NULL)
498 		fAddOnShellView->AddChild(nextView);
499 }
500 
501 
502 void
503 NetworkWindow::_BroadcastSettingsUpdate(uint32 type)
504 {
505 	for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
506 		BNetworkSettingsListener* listener
507 			= dynamic_cast<BNetworkSettingsListener*>(
508 				fListView->FullListItemAt(index));
509 		if (listener != NULL)
510 			listener->SettingsUpdated(type);
511 	}
512 
513 	SettingsMap::const_iterator iterator = fSettingsMap.begin();
514 	for (; iterator != fSettingsMap.end(); iterator++)
515 		iterator->second->SettingsUpdated(type);
516 }
517 
518 
519 void
520 NetworkWindow::_BroadcastConfigurationUpdate(const BMessage& message)
521 {
522 	for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
523 		BNetworkConfigurationListener* listener
524 			= dynamic_cast<BNetworkConfigurationListener*>(
525 				fListView->FullListItemAt(index));
526 		if (listener != NULL)
527 			listener->ConfigurationUpdated(message);
528 	}
529 
530 	SettingsMap::const_iterator iterator = fSettingsMap.begin();
531 	for (; iterator != fSettingsMap.end(); iterator++)
532 		iterator->second->ConfigurationUpdated(message);
533 
534 	// TODO: improve invalidated region to the one that matters
535 	fListView->Invalidate();
536 }
537 
538 
539 void
540 NetworkWindow::_ShowReplicant(bool show)
541 {
542 	if (show) {
543 		const char* argv[] = {"--deskbar", NULL};
544 
545 		status_t status = be_roster->Launch(kNetworkStatusSignature, 1, argv);
546 		if (status != B_OK) {
547 			BString errorMessage;
548 			errorMessage.SetToFormat(
549 				B_TRANSLATE("Installing NetworkStatus in Deskbar failed: %s"),
550 				strerror(status));
551 			BAlert* alert = new BAlert(B_TRANSLATE("launch error"),
552 				errorMessage, B_TRANSLATE("Ok"));
553 			alert->Go(NULL);
554 		}
555 	} else {
556 		BDeskbar deskbar;
557 		deskbar.RemoveItem("NetworkStatus");
558 	}
559 }
560 
561 
562 bool
563 NetworkWindow::_IsReplicantInstalled()
564 {
565 	BDeskbar deskbar;
566 	return deskbar.HasItem("NetworkStatus");
567 }
568 
569 
570 /*static*/ const char*
571 NetworkWindow::_ItemName(const BListItem* item)
572 {
573 	if (const BNetworkInterfaceListItem* listItem = dynamic_cast<
574 			const BNetworkInterfaceListItem*>(item))
575 		return listItem->Label();
576 
577 	if (const BStringItem* stringItem = dynamic_cast<const BStringItem*>(item))
578 		return stringItem->Text();
579 
580 	return NULL;
581 }
582 
583 
584 /*static*/ int
585 NetworkWindow::_CompareTopLevelListItems(const BListItem* a, const BListItem* b)
586 {
587 	if (a == b)
588 		return 0;
589 
590 	if (const InterfaceListItem* itemA
591 			= dynamic_cast<const InterfaceListItem*>(a)) {
592 		if (const InterfaceListItem* itemB
593 				= dynamic_cast<const InterfaceListItem*>(b)) {
594 			return strcasecmp(itemA->Name(), itemB->Name());
595 		}
596 		return -1;
597 	} else if (dynamic_cast<const InterfaceListItem*>(b) != NULL)
598 		return 1;
599 /*
600 	if (a == fDialUpItem)
601 		return -1;
602 	if (b == fDialUpItem)
603 		return 1;
604 
605 	if (a == fServicesItem)
606 		return -1;
607 	if (b == fServicesItem)
608 		return 1;
609 */
610 	return _CompareListItems(a, b);
611 }
612 
613 
614 /*static*/ int
615 NetworkWindow::_CompareListItems(const BListItem* a, const BListItem* b)
616 {
617 	if (a == b)
618 		return 0;
619 
620 	const char* nameA = _ItemName(a);
621 	const char* nameB = _ItemName(b);
622 
623 	if (nameA != NULL && nameB != NULL)
624 		return strcasecmp(nameA, nameB);
625 	if (nameA != NULL)
626 		return 1;
627 	if (nameB != NULL)
628 		return -1;
629 
630 	return (addr_t)a > (addr_t)b ? 1 : -1;
631 }
632