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