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