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