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
51const char* kNetworkStatusSignature = "application/x-vnd.Haiku-NetworkStatus";
52
53static const uint32 kMsgProfileSelected = 'prof';
54static const uint32 kMsgProfileManage = 'mngp';
55static const uint32 kMsgProfileNew = 'newp';
56static const uint32 kMsgRevert = 'rvrt';
57static const uint32 kMsgToggleReplicant = 'trep';
58static const uint32 kMsgItemSelected = 'ItSl';
59
60BMessenger gNetworkWindow;
61
62
63#undef B_TRANSLATION_CONTEXT
64#define B_TRANSLATION_CONTEXT	"NetworkWindow"
65
66
67class TitleItem : public BStringItem {
68public:
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
92NetworkWindow::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
189NetworkWindow::~NetworkWindow()
190{
191	stop_watching_network(this);
192	fSettings.StopMonitoring(this);
193}
194
195
196bool
197NetworkWindow::QuitRequested()
198{
199	be_app->PostMessage(B_QUIT_REQUESTED);
200	return true;
201}
202
203
204void
205NetworkWindow::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
274void
275NetworkWindow::_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
332void
333NetworkWindow::_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
364void
365NetworkWindow::_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
463BNetworkSettingsItem*
464NetworkWindow::_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
474void
475NetworkWindow::_SortItemsUnder(BListItem* item)
476{
477	if (item != NULL)
478		fListView->SortItemsUnder(item, true, NetworkWindow::_CompareListItems);
479}
480
481
482BListItem*
483NetworkWindow::_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
502BListItem*
503NetworkWindow::_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
512void
513NetworkWindow::_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
537void
538NetworkWindow::_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
556void
557NetworkWindow::_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
577void
578NetworkWindow::_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
593void
594NetworkWindow::_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
616bool
617NetworkWindow::_IsReplicantInstalled()
618{
619	BDeskbar deskbar;
620	return deskbar.HasItem("NetworkStatus");
621}
622
623
624/*static*/ const char*
625NetworkWindow::_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
643NetworkWindow::_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
673NetworkWindow::_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