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