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