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