/* * Copyright 2004-2019 Haiku Inc., All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Adrien Destugues, * Axel Dörfler, * Alexander von Gluck, */ #include "NetworkWindow.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ENABLE_PROFILES 0 #if ENABLE_PROFILES # include #endif #include "InterfaceListItem.h" #include "InterfaceView.h" #include "ServiceListItem.h" const char* kNetworkStatusSignature = "application/x-vnd.Haiku-NetworkStatus"; static const uint32 kMsgProfileSelected = 'prof'; static const uint32 kMsgProfileManage = 'mngp'; static const uint32 kMsgProfileNew = 'newp'; static const uint32 kMsgRevert = 'rvrt'; static const uint32 kMsgToggleReplicant = 'trep'; static const uint32 kMsgItemSelected = 'ItSl'; BMessenger gNetworkWindow; #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "NetworkWindow" class TitleItem : public BStringItem { public: TitleItem(const char* title) : BStringItem(title) { } void DrawItem(BView* owner, BRect bounds, bool complete) { owner->SetFont(be_bold_font); BStringItem::DrawItem(owner, bounds, complete); owner->SetFont(be_plain_font); } void Update(BView* owner, const BFont* font) { BStringItem::Update(owner, be_bold_font); } }; // #pragma mark - NetworkWindow::NetworkWindow() : BWindow(BRect(100, 100, 750, 400), B_TRANSLATE_SYSTEM_NAME("Network"), B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS), fServicesItem(NULL), fDialUpItem(NULL), fVPNItem(NULL), fOtherItem(NULL) { // Profiles section #if ENABLE_PROFILES BPopUpMenu* profilesPopup = new BPopUpMenu(""); _BuildProfilesMenu(profilesPopup, kMsgProfileSelected); BMenuField* profilesMenuField = new BMenuField("profiles_menu", B_TRANSLATE("Profile:"), profilesPopup); profilesMenuField->SetFont(be_bold_font); profilesMenuField->SetEnabled(false); #endif // Settings section fRevertButton = new BButton("revert", B_TRANSLATE("Revert"), new BMessage(kMsgRevert)); BMessage* message = new BMessage(kMsgToggleReplicant); BCheckBox* showReplicantCheckBox = new BCheckBox("showReplicantCheckBox", B_TRANSLATE("Show network status in Deskbar"), message); showReplicantCheckBox->SetExplicitMaxSize( BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); showReplicantCheckBox->SetValue(_IsReplicantInstalled()); fListView = new BOutlineListView("list", B_SINGLE_SELECTION_LIST, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS | B_NAVIGABLE); fListView->SetSelectionMessage(new BMessage(kMsgItemSelected)); BScrollView* scrollView = new BScrollView("ScrollView", fListView, 0, false, true); scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED)); fAddOnShellView = new BView("add-on shell", 0, new BGroupLayout(B_VERTICAL)); fAddOnShellView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); fAddOnShellView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); fInterfaceView = new InterfaceView(); // Build the layout BLayoutBuilder::Group<>(this, B_VERTICAL) .SetInsets(B_USE_WINDOW_SPACING) #if ENABLE_PROFILES .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING) .Add(profilesMenuField) .AddGlue() .End() #endif .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING) .Add(scrollView) .Add(fAddOnShellView) .End() .Add(showReplicantCheckBox) .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING) .Add(fRevertButton) .AddGlue() .End(); gNetworkWindow = this; _ScanInterfaces(); _ScanAddOns(); _UpdateRevertButton(); fListView->Select(0); _SelectItem(fListView->ItemAt(0)); // Call this manually, so that CenterOnScreen() below already // knows the final window size. // Set size of the list view from its contents float width; float height; fListView->GetPreferredSize(&width, &height); width += 2 * be_control_look->DefaultItemSpacing(); fListView->SetExplicitSize(BSize(width, B_SIZE_UNSET)); fListView->SetExplicitMinSize(BSize(width, std::min(height, 400.f))); CenterOnScreen(); fSettings.StartMonitoring(this); start_watching_network(B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES | B_WATCH_NETWORK_WLAN_CHANGES, this); } NetworkWindow::~NetworkWindow() { stop_watching_network(this); fSettings.StopMonitoring(this); } bool NetworkWindow::QuitRequested() { be_app->PostMessage(B_QUIT_REQUESTED); return true; } void NetworkWindow::MessageReceived(BMessage* message) { switch (message->what) { case kMsgProfileNew: break; case kMsgProfileSelected: { const char* path; if (message->FindString("path", &path) != B_OK) break; // TODO! break; } case kMsgItemSelected: { BListItem* listItem = fListView->FullListItemAt( fListView->FullListCurrentSelection()); if (listItem == NULL) break; _SelectItem(listItem); break; } case kMsgRevert: { SettingsMap::const_iterator iterator = fSettingsMap.begin(); for (; iterator != fSettingsMap.end(); iterator++) iterator->second->Revert(); break; } case kMsgToggleReplicant: { _ShowReplicant( message->GetInt32("be:value", B_CONTROL_OFF) == B_CONTROL_ON); break; } case B_PATH_MONITOR: { fSettings.Update(message); break; } case B_NETWORK_MONITOR: _BroadcastConfigurationUpdate(*message); break; case BNetworkSettings::kMsgInterfaceSettingsUpdated: case BNetworkSettings::kMsgNetworkSettingsUpdated: case BNetworkSettings::kMsgServiceSettingsUpdated: _BroadcastSettingsUpdate(message->what); break; case kMsgSettingsItemUpdated: // TODO: update list item _UpdateRevertButton(); break; default: inherited::MessageReceived(message); } } void NetworkWindow::_BuildProfilesMenu(BMenu* menu, int32 what) { char currentProfile[256] = { 0 }; menu->SetRadioMode(true); BDirectory dir("/boot/system/settings/network/profiles"); if (dir.InitCheck() == B_OK) { BEntry entry; dir.Rewind(); while (dir.GetNextEntry(&entry) >= 0) { BPath name; entry.GetPath(&name); if (entry.IsSymLink() && strcmp("current", name.Leaf()) == 0) { BSymLink symlink(&entry); if (symlink.IsAbsolute()) // oh oh, sorry, wrong symlink... continue; symlink.ReadLink(currentProfile, sizeof(currentProfile)); continue; }; if (!entry.IsDirectory()) continue; BMessage* message = new BMessage(what); message->AddString("path", name.Path()); BMenuItem* item = new BMenuItem(name.Leaf(), message); menu->AddItem(item); } } menu->AddSeparatorItem(); menu->AddItem(new BMenuItem(B_TRANSLATE("New" B_UTF8_ELLIPSIS), new BMessage(kMsgProfileNew))); menu->AddItem(new BMenuItem(B_TRANSLATE("Manage" B_UTF8_ELLIPSIS), new BMessage(kMsgProfileManage))); if (currentProfile[0] != '\0') { BMenuItem* item = menu->FindItem(currentProfile); if (item != NULL) { // TODO: translate BString label(item->Label()); label << " (current)"; item->SetLabel(label.String()); item->SetMarked(true); } } } void NetworkWindow::_ScanInterfaces() { // Try existing devices first BNetworkRoster& roster = BNetworkRoster::Default(); BNetworkInterface interface; uint32 cookie = 0; while (roster.GetNextInterface(&cookie, interface) == B_OK) { if ((interface.Flags() & IFF_LOOPBACK) != 0) continue; BNetworkDevice device(interface.Name()); BNetworkInterfaceType type = B_NETWORK_INTERFACE_TYPE_OTHER; if (device.IsWireless()) type = B_NETWORK_INTERFACE_TYPE_WIFI; else if (device.IsEthernet()) type = B_NETWORK_INTERFACE_TYPE_ETHERNET; InterfaceListItem* item = new InterfaceListItem(interface.Name(), type); item->SetExpanded(true); fInterfaceItemMap.insert(std::pair( BString(interface.Name()), item)); fListView->AddItem(item); } // TODO: Then consider those from the settings (for example, for USB) } void NetworkWindow::_ScanAddOns() { BStringList paths; BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Network Settings", paths); // Collect add-on paths by name, so that each name will only be // loaded once. typedef std::map PathMap; PathMap addOnMap; for (int32 i = 0; i < paths.CountStrings(); i++) { BDirectory directory(paths.StringAt(i)); BEntry entry; while (directory.GetNextEntry(&entry) == B_OK) { BPath path; if (entry.GetPath(&path) != B_OK) continue; if (addOnMap.find(path.Leaf()) == addOnMap.end()) addOnMap.insert(std::pair(path.Leaf(), path)); } } for (PathMap::const_iterator addOnIterator = addOnMap.begin(); addOnIterator != addOnMap.end(); addOnIterator++) { const BPath& path = addOnIterator->second; image_id image = load_add_on(path.Path()); if (image < 0) { printf("Failed to load %s addon: %s.\n", path.Path(), strerror(image)); continue; } BNetworkSettingsAddOn* (*instantiateAddOn)(image_id image, BNetworkSettings& settings); status_t status = get_image_symbol(image, "instantiate_network_settings_add_on", B_SYMBOL_TYPE_TEXT, (void**)&instantiateAddOn); if (status != B_OK) { // No "addon instantiate function" symbol found in this addon printf("No symbol \"instantiate_network_settings_add_on\" found " "in %s addon: not a network setup addon!\n", path.Path()); unload_add_on(image); continue; } BNetworkSettingsAddOn* addOn = instantiateAddOn(image, fSettings); if (addOn == NULL) { unload_add_on(image); continue; } fAddOns.AddItem(addOn); // Per interface items ItemMap::const_iterator iterator = fInterfaceItemMap.begin(); for (; iterator != fInterfaceItemMap.end(); iterator++) { const BString& interface = iterator->first; BListItem* interfaceItem = iterator->second; uint32 cookie = 0; while (true) { BNetworkSettingsItem* item = addOn->CreateNextInterfaceItem( cookie, interface.String()); if (item == NULL) break; fSettingsMap[item->ListItem()] = item; fListView->AddUnder(item->ListItem(), interfaceItem); } fListView->SortItemsUnder(interfaceItem, true, NetworkWindow::_CompareListItems); } // Generic items uint32 cookie = 0; while (true) { BNetworkSettingsItem* item = addOn->CreateNextItem(cookie); if (item == NULL) break; fSettingsMap[item->ListItem()] = item; fListView->AddUnder(item->ListItem(), _ListItemFor(item->Type())); } _SortItemsUnder(fServicesItem); _SortItemsUnder(fOtherItem); } fListView->SortItemsUnder(NULL, true, NetworkWindow::_CompareTopLevelListItems); } BNetworkSettingsItem* NetworkWindow::_SettingsItemFor(BListItem* item) { SettingsMap::const_iterator found = fSettingsMap.find(item); if (found != fSettingsMap.end()) return found->second; return NULL; } void NetworkWindow::_SortItemsUnder(BListItem* item) { if (item != NULL) fListView->SortItemsUnder(item, true, NetworkWindow::_CompareListItems); } BListItem* NetworkWindow::_ListItemFor(BNetworkSettingsType type) { switch (type) { case B_NETWORK_SETTINGS_TYPE_SERVICE: if (fServicesItem == NULL) fServicesItem = _CreateItem(B_TRANSLATE("Services")); return fServicesItem; case B_NETWORK_SETTINGS_TYPE_OTHER: if (fOtherItem == NULL) fOtherItem = _CreateItem(B_TRANSLATE("Other")); return fOtherItem; default: return NULL; } } BListItem* NetworkWindow::_CreateItem(const char* label) { BListItem* item = new TitleItem(label); item->SetExpanded(true); fListView->AddItem(item); return item; } void NetworkWindow::_SelectItem(BListItem* listItem) { while (fAddOnShellView->CountChildren() > 0) fAddOnShellView->ChildAt(0)->RemoveSelf(); BView* nextView = NULL; BNetworkSettingsItem* item = _SettingsItemFor(listItem); if (item != NULL) { nextView = item->View(); } else { InterfaceListItem* item = dynamic_cast( listItem); if (item != NULL) { fInterfaceView->SetTo(item->Name()); nextView = fInterfaceView; } } if (nextView != NULL) fAddOnShellView->AddChild(nextView); } void NetworkWindow::_BroadcastSettingsUpdate(uint32 type) { for (int32 index = 0; index < fListView->FullListCountItems(); index++) { BNetworkSettingsListener* listener = dynamic_cast( fListView->FullListItemAt(index)); if (listener != NULL) listener->SettingsUpdated(type); } SettingsMap::const_iterator iterator = fSettingsMap.begin(); for (; iterator != fSettingsMap.end(); iterator++) iterator->second->SettingsUpdated(type); _UpdateRevertButton(); } void NetworkWindow::_BroadcastConfigurationUpdate(const BMessage& message) { for (int32 index = 0; index < fListView->FullListCountItems(); index++) { BNetworkConfigurationListener* listener = dynamic_cast( fListView->FullListItemAt(index)); if (listener != NULL) listener->ConfigurationUpdated(message); } SettingsMap::const_iterator iterator = fSettingsMap.begin(); for (; iterator != fSettingsMap.end(); iterator++) iterator->second->ConfigurationUpdated(message); // TODO: improve invalidated region to the one that matters fListView->Invalidate(); _UpdateRevertButton(); } void NetworkWindow::_UpdateRevertButton() { bool enabled = false; SettingsMap::const_iterator iterator = fSettingsMap.begin(); for (; iterator != fSettingsMap.end(); iterator++) { if (iterator->second->IsRevertable()) { enabled = true; break; } } fRevertButton->SetEnabled(enabled); } void NetworkWindow::_ShowReplicant(bool show) { if (show) { const char* argv[] = {"--deskbar", NULL}; status_t status = be_roster->Launch(kNetworkStatusSignature, 1, argv); if (status != B_OK) { BString errorMessage; errorMessage.SetToFormat( B_TRANSLATE("Installing NetworkStatus in Deskbar failed: %s"), strerror(status)); BAlert* alert = new BAlert(B_TRANSLATE("launch error"), errorMessage, B_TRANSLATE("OK")); alert->Go(NULL); } } else { BDeskbar deskbar; deskbar.RemoveItem("NetworkStatus"); } } bool NetworkWindow::_IsReplicantInstalled() { BDeskbar deskbar; return deskbar.HasItem("NetworkStatus"); } /*static*/ const char* NetworkWindow::_ItemName(const BListItem* item) { if (const BNetworkInterfaceListItem* listItem = dynamic_cast< const BNetworkInterfaceListItem*>(item)) return listItem->Label(); if (const ServiceListItem* listItem = dynamic_cast< const ServiceListItem*>(item)) return listItem->Label(); if (const BStringItem* stringItem = dynamic_cast(item)) return stringItem->Text(); return NULL; } /*static*/ int NetworkWindow::_CompareTopLevelListItems(const BListItem* a, const BListItem* b) { if (a == b) return 0; if (const InterfaceListItem* itemA = dynamic_cast(a)) { if (const InterfaceListItem* itemB = dynamic_cast(b)) { return strcasecmp(itemA->Name(), itemB->Name()); } return -1; } else if (dynamic_cast(b) != NULL) return 1; /* if (a == fDialUpItem) return -1; if (b == fDialUpItem) return 1; if (a == fServicesItem) return -1; if (b == fServicesItem) return 1; */ return _CompareListItems(a, b); } /*static*/ int NetworkWindow::_CompareListItems(const BListItem* a, const BListItem* b) { if (a == b) return 0; const char* nameA = _ItemName(a); const char* nameB = _ItemName(b); if (nameA != NULL && nameB != NULL) return strcasecmp(nameA, nameB); if (nameA != NULL) return 1; if (nameB != NULL) return -1; return (addr_t)a > (addr_t)b ? 1 : -1; }