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