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