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