1 /* 2 * Copyright 2006-2013, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Dario Casalinuovo 7 * Axel Dörfler, axeld@pinc-software.de 8 * Rene Gollent, rene@gollent.com 9 * Hugo Santos, hugosantos@gmail.com 10 */ 11 12 13 #include "NetworkStatusView.h" 14 15 #include <algorithm> 16 #include <set> 17 #include <vector> 18 19 #include <arpa/inet.h> 20 #include <net/if.h> 21 #include <stdio.h> 22 #include <stdlib.h> 23 #include <strings.h> 24 #include <sys/socket.h> 25 #include <sys/sockio.h> 26 #include <unistd.h> 27 28 #include <AboutWindow.h> 29 #include <Alert.h> 30 #include <Application.h> 31 #include <Catalog.h> 32 #include <Bitmap.h> 33 #include <Deskbar.h> 34 #include <Dragger.h> 35 #include <Drivers.h> 36 #include <IconUtils.h> 37 #include <Locale.h> 38 #include <MenuItem.h> 39 #include <MessageRunner.h> 40 #include <NetworkInterface.h> 41 #include <NetworkRoster.h> 42 #include <PopUpMenu.h> 43 #include <Resources.h> 44 #include <Roster.h> 45 #include <String.h> 46 #include <TextView.h> 47 48 #include "NetworkStatus.h" 49 #include "NetworkStatusIcons.h" 50 #include "RadioView.h" 51 #include "WirelessNetworkMenuItem.h" 52 53 54 #undef B_TRANSLATION_CONTEXT 55 #define B_TRANSLATION_CONTEXT "NetworkStatusView" 56 57 58 static const char *kStatusDescriptions[] = { 59 B_TRANSLATE("Unknown"), 60 B_TRANSLATE("No link"), 61 B_TRANSLATE("No stateful configuration"), 62 B_TRANSLATE("Configuring"), 63 B_TRANSLATE("Ready") 64 }; 65 66 extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth, float maxHeight); 67 68 69 const uint32 kMsgShowConfiguration = 'shcf'; 70 const uint32 kMsgOpenNetworkPreferences = 'onwp'; 71 const uint32 kMsgJoinNetwork = 'join'; 72 73 const uint32 kMinIconWidth = 16; 74 const uint32 kMinIconHeight = 16; 75 76 77 // #pragma mark - NetworkStatusView 78 79 80 NetworkStatusView::NetworkStatusView(BRect frame, int32 resizingMode, 81 bool inDeskbar) 82 : BView(frame, kDeskbarItemName, resizingMode, 83 B_WILL_DRAW | B_TRANSPARENT_BACKGROUND | B_FRAME_EVENTS), 84 fInDeskbar(inDeskbar) 85 { 86 _Init(); 87 88 if (!inDeskbar) { 89 // we were obviously added to a standard window - let's add a dragger 90 frame.OffsetTo(B_ORIGIN); 91 frame.top = frame.bottom - 7; 92 frame.left = frame.right - 7; 93 BDragger* dragger = new BDragger(frame, this, 94 B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM); 95 AddChild(dragger); 96 } else 97 _Update(); 98 } 99 100 101 NetworkStatusView::NetworkStatusView(BMessage* archive) 102 : BView(archive), 103 fInDeskbar(false) 104 { 105 app_info info; 106 if (be_app->GetAppInfo(&info) == B_OK 107 && !strcasecmp(info.signature, "application/x-vnd.Be-TSKB")) 108 fInDeskbar = true; 109 110 _Init(); 111 } 112 113 114 NetworkStatusView::~NetworkStatusView() 115 { 116 } 117 118 119 void 120 NetworkStatusView::_Init() 121 { 122 for (int i = 0; i < kStatusCount; i++) { 123 fTrayIcons[i] = NULL; 124 fNotifyIcons[i] = NULL; 125 } 126 127 _UpdateBitmaps(); 128 } 129 130 131 void 132 NetworkStatusView::_UpdateBitmaps() 133 { 134 for (int i = 0; i < kStatusCount; i++) { 135 delete fTrayIcons[i]; 136 delete fNotifyIcons[i]; 137 fTrayIcons[i] = NULL; 138 fNotifyIcons[i] = NULL; 139 } 140 141 image_info info; 142 if (our_image(info) != B_OK) 143 return; 144 145 BFile file(info.name, B_READ_ONLY); 146 if (file.InitCheck() < B_OK) 147 return; 148 149 BResources resources(&file); 150 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 151 if (resources.InitCheck() < B_OK) 152 return; 153 #endif 154 155 for (int i = 0; i < kStatusCount; i++) { 156 const void* data = NULL; 157 size_t size; 158 data = resources.LoadResource(B_VECTOR_ICON_TYPE, 159 kNetworkStatusNoDevice + i, &size); 160 if (data != NULL) { 161 // Scale main tray icon 162 BBitmap* trayIcon = new BBitmap(Bounds(), B_RGBA32); 163 if (trayIcon->InitCheck() == B_OK 164 && BIconUtils::GetVectorIcon((const uint8 *)data, 165 size, trayIcon) == B_OK) { 166 fTrayIcons[i] = trayIcon; 167 } else 168 delete trayIcon; 169 170 // Scale notification icon 171 BBitmap* notifyIcon = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32); 172 if (notifyIcon->InitCheck() == B_OK 173 && BIconUtils::GetVectorIcon((const uint8 *)data, 174 size, notifyIcon) == B_OK) { 175 fNotifyIcons[i] = notifyIcon; 176 } else 177 delete notifyIcon; 178 } 179 } 180 } 181 182 183 void 184 NetworkStatusView::_Quit() 185 { 186 if (fInDeskbar) { 187 BDeskbar deskbar; 188 deskbar.RemoveItem(kDeskbarItemName); 189 } else 190 be_app->PostMessage(B_QUIT_REQUESTED); 191 } 192 193 194 NetworkStatusView* 195 NetworkStatusView::Instantiate(BMessage* archive) 196 { 197 if (!validate_instantiation(archive, "NetworkStatusView")) 198 return NULL; 199 200 return new NetworkStatusView(archive); 201 } 202 203 204 status_t 205 NetworkStatusView::Archive(BMessage* archive, bool deep) const 206 { 207 status_t status = BView::Archive(archive, deep); 208 if (status == B_OK) 209 status = archive->AddString("add_on", kSignature); 210 if (status == B_OK) 211 status = archive->AddString("class", "NetworkStatusView"); 212 213 return status; 214 } 215 216 217 void 218 NetworkStatusView::AttachedToWindow() 219 { 220 BView::AttachedToWindow(); 221 222 SetViewColor(B_TRANSPARENT_COLOR); 223 224 start_watching_network( 225 B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this); 226 227 _Update(); 228 } 229 230 231 void 232 NetworkStatusView::DetachedFromWindow() 233 { 234 stop_watching_network(this); 235 } 236 237 238 void 239 NetworkStatusView::MessageReceived(BMessage* message) 240 { 241 switch (message->what) { 242 case B_NETWORK_MONITOR: 243 _Update(); 244 break; 245 246 case kMsgShowConfiguration: 247 _ShowConfiguration(message); 248 break; 249 250 case kMsgOpenNetworkPreferences: 251 _OpenNetworksPreferences(); 252 break; 253 254 case kMsgJoinNetwork: 255 { 256 const char* deviceName; 257 const char* name; 258 BNetworkAddress address; 259 if (message->FindString("device", &deviceName) == B_OK 260 && message->FindString("name", &name) == B_OK 261 && message->FindFlat("address", &address) == B_OK) { 262 BNetworkDevice device(deviceName); 263 status_t status = device.JoinNetwork(address); 264 if (status != B_OK) { 265 BString text 266 = B_TRANSLATE("Could not join wireless network:\n"); 267 text << strerror(status); 268 BAlert* alert = new BAlert(name, text.String(), 269 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 270 B_STOP_ALERT); 271 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 272 alert->Go(NULL); 273 } 274 } 275 break; 276 } 277 278 case B_ABOUT_REQUESTED: 279 _AboutRequested(); 280 break; 281 282 case B_QUIT_REQUESTED: 283 _Quit(); 284 break; 285 286 default: 287 BView::MessageReceived(message); 288 break; 289 } 290 } 291 292 293 void 294 NetworkStatusView::FrameResized(float width, float height) 295 { 296 _UpdateBitmaps(); 297 Invalidate(); 298 } 299 300 301 void 302 NetworkStatusView::Draw(BRect updateRect) 303 { 304 int32 status = kStatusUnknown; 305 for (std::map<BString, int32>::const_iterator it 306 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 307 if (it->second > status) 308 status = it->second; 309 } 310 311 if (fTrayIcons[status] == NULL) 312 return; 313 314 SetDrawingMode(B_OP_ALPHA); 315 DrawBitmap(fTrayIcons[status]); 316 SetDrawingMode(B_OP_COPY); 317 } 318 319 320 void 321 NetworkStatusView::_ShowConfiguration(BMessage* message) 322 { 323 const char* name; 324 if (message->FindString("interface", &name) != B_OK) 325 return; 326 327 BNetworkInterface networkInterface(name); 328 if (!networkInterface.Exists()) 329 return; 330 331 BString text(B_TRANSLATE("%ifaceName information:\n")); 332 text.ReplaceFirst("%ifaceName", name); 333 334 size_t boldLength = text.Length(); 335 336 int32 numAddrs = networkInterface.CountAddresses(); 337 for (int32 i = 0; i < numAddrs; i++) { 338 BNetworkInterfaceAddress address; 339 networkInterface.GetAddressAt(i, address); 340 switch (address.Address().Family()) { 341 case AF_INET: 342 text << "\n" << B_TRANSLATE("IPv4 address:") << " " 343 << address.Address().ToString() 344 << "\n" << B_TRANSLATE("Broadcast:") << " " 345 << address.Broadcast().ToString() 346 << "\n" << B_TRANSLATE("Netmask:") << " " 347 << address.Mask().ToString() 348 << "\n"; 349 break; 350 case AF_INET6: 351 text << "\n" << B_TRANSLATE("IPv6 address:") << " " 352 << address.Address().ToString() 353 << "/" << address.Mask().PrefixLength() 354 << "\n"; 355 break; 356 default: 357 break; 358 } 359 } 360 361 BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK")); 362 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 363 BTextView* view = alert->TextView(); 364 BFont font; 365 366 view->SetStylable(true); 367 view->GetFont(&font); 368 font.SetFace(B_BOLD_FACE); 369 view->SetFontAndColor(0, boldLength, &font); 370 371 alert->Go(NULL); 372 } 373 374 375 void 376 NetworkStatusView::MouseDown(BPoint point) 377 { 378 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 379 menu->SetAsyncAutoDestruct(true); 380 menu->SetFont(be_plain_font); 381 BString wifiInterface; 382 BNetworkDevice device; 383 384 if (!fInterfaceStatuses.empty()) { 385 for (std::map<BString, int32>::const_iterator it 386 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); 387 ++it) { 388 const BString& name = it->first; 389 390 // we only show network of the first wireless device we find 391 if (wifiInterface.IsEmpty()) { 392 device.SetTo(name); 393 if (device.IsWireless()) 394 wifiInterface = name; 395 } 396 } 397 } 398 399 // Add wireless networks, if any, first so that we can sort the menu 400 401 if (!wifiInterface.IsEmpty()) { 402 std::set<BNetworkAddress> associated; 403 BNetworkAddress address; 404 uint32 cookie = 0; 405 while (device.GetNextAssociatedNetwork(cookie, address) == B_OK) 406 associated.insert(address); 407 408 uint32 networksCount = 0; 409 wireless_network* networks = NULL; 410 device.GetNetworks(networks, networksCount); 411 for (uint32 i = 0; i < networksCount; i++) { 412 const wireless_network& network = networks[i]; 413 BMessage* message = new BMessage(kMsgJoinNetwork); 414 message->AddString("device", wifiInterface); 415 message->AddString("name", network.name); 416 message->AddFlat("address", &network.address); 417 418 BMenuItem* item = new WirelessNetworkMenuItem(network, message); 419 menu->AddItem(item); 420 if (associated.find(network.address) != associated.end()) 421 item->SetMarked(true); 422 } 423 delete[] networks; 424 425 if (networksCount == 0) { 426 BMenuItem* item = new BMenuItem( 427 B_TRANSLATE("<no wireless networks found>"), NULL); 428 item->SetEnabled(false); 429 menu->AddItem(item); 430 } else 431 menu->SortItems(WirelessNetworkMenuItem::CompareSignalStrength); 432 433 menu->AddSeparatorItem(); 434 } 435 436 // add action menu items 437 438 menu->AddItem(new BMenuItem(B_TRANSLATE( 439 "Open network preferences" B_UTF8_ELLIPSIS), 440 new BMessage(kMsgOpenNetworkPreferences))); 441 442 if (fInDeskbar) { 443 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 444 new BMessage(B_QUIT_REQUESTED))); 445 } 446 447 // Add wired interfaces to top of menu 448 if (!fInterfaceStatuses.empty()) { 449 int32 wiredCount = 0; 450 for (std::map<BString, int32>::const_iterator it 451 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); 452 ++it) { 453 const BString& name = it->first; 454 455 BString label = name; 456 label += ": "; 457 label += kStatusDescriptions[ 458 _DetermineInterfaceStatus(name.String())]; 459 460 BMessage* info = new BMessage(kMsgShowConfiguration); 461 info->AddString("interface", name.String()); 462 menu->AddItem(new BMenuItem(label.String(), info), wiredCount); 463 wiredCount++; 464 } 465 466 // add separator item between wired and wireless networks 467 // (or between wired networks and actions if no wireless found) 468 if (wiredCount > 0) 469 menu->AddItem(new BSeparatorItem(), wiredCount); 470 } 471 472 menu->SetTargetForItems(this); 473 474 ConvertToScreen(&point); 475 menu->Go(point, true, true, true); 476 } 477 478 479 void 480 NetworkStatusView::_AboutRequested() 481 { 482 BAboutWindow* window = new BAboutWindow( 483 B_TRANSLATE_SYSTEM_NAME("NetworkStatus"), kSignature); 484 485 const char* authors[] = { 486 "Axel Dörfler", 487 "Hugo Santos", 488 NULL 489 }; 490 491 window->AddCopyright(2007, "Haiku, Inc."); 492 window->AddAuthors(authors); 493 494 window->Show(); 495 } 496 497 498 int32 499 NetworkStatusView::_DetermineInterfaceStatus( 500 const BNetworkInterface& interface) 501 { 502 uint32 flags = interface.Flags(); 503 504 if ((flags & IFF_LINK) == 0) 505 return kStatusNoLink; 506 if ((flags & (IFF_UP | IFF_LINK | IFF_CONFIGURING)) == IFF_LINK) 507 return kStatusLinkNoConfig; 508 if ((flags & IFF_CONFIGURING) == IFF_CONFIGURING) 509 return kStatusConnecting; 510 if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) 511 return kStatusReady; 512 513 return kStatusUnknown; 514 } 515 516 517 void 518 NetworkStatusView::_Update(bool force) 519 { 520 BNetworkRoster& roster = BNetworkRoster::Default(); 521 BNetworkInterface interface; 522 uint32 cookie = 0; 523 std::set<BString> currentInterfaces; 524 525 while (roster.GetNextInterface(&cookie, interface) == B_OK) { 526 if ((interface.Flags() & IFF_LOOPBACK) == 0) { 527 currentInterfaces.insert((BString)interface.Name()); 528 int32 oldStatus = kStatusUnknown; 529 if (fInterfaceStatuses.find(interface.Name()) 530 != fInterfaceStatuses.end()) { 531 oldStatus = fInterfaceStatuses[interface.Name()]; 532 } 533 int32 status = _DetermineInterfaceStatus(interface); 534 if (oldStatus != status) { 535 BNotification notification(B_INFORMATION_NOTIFICATION); 536 notification.SetGroup(B_TRANSLATE("Network Status")); 537 notification.SetTitle(interface.Name()); 538 notification.SetMessageID(interface.Name()); 539 notification.SetIcon(fNotifyIcons[status]); 540 if (status == kStatusConnecting 541 || (status == kStatusReady 542 && oldStatus == kStatusConnecting) 543 || (status == kStatusNoLink 544 && oldStatus == kStatusReady) 545 || (status == kStatusNoLink 546 && oldStatus == kStatusConnecting)) { 547 // A significant state change, raise notification. 548 notification.SetContent(kStatusDescriptions[status]); 549 notification.Send(); 550 } 551 Invalidate(); 552 } 553 fInterfaceStatuses[interface.Name()] = status; 554 } 555 } 556 557 // Check every element in fInterfaceStatuses against our current interface 558 // list. If it's not there, then the interface is not present anymore and 559 // should be removed from fInterfaceStatuses. 560 std::map<BString, int32>::iterator it = fInterfaceStatuses.begin(); 561 while (it != fInterfaceStatuses.end()) { 562 std::map<BString, int32>::iterator backupIt = it; 563 if (currentInterfaces.find(it->first) == currentInterfaces.end()) 564 fInterfaceStatuses.erase(it); 565 it = ++backupIt; 566 } 567 } 568 569 570 void 571 NetworkStatusView::_OpenNetworksPreferences() 572 { 573 status_t status = be_roster->Launch("application/x-vnd.Haiku-Network"); 574 if (status != B_OK && status != B_ALREADY_RUNNING) { 575 BString errorMessage(B_TRANSLATE("Launching the network preflet " 576 "failed.\n\nError: ")); 577 errorMessage << strerror(status); 578 BAlert* alert = new BAlert("launch error", errorMessage.String(), 579 B_TRANSLATE("OK")); 580 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 581 582 // asynchronous alert in order to not block replicant host application 583 alert->Go(NULL); 584 } 585 } 586 587 588 // #pragma mark - 589 590 591 extern "C" _EXPORT BView * 592 instantiate_deskbar_item(float maxWidth, float maxHeight) 593 { 594 return new NetworkStatusView(BRect(0, 0, maxHeight - 1, maxHeight - 1), 595 B_FOLLOW_LEFT | B_FOLLOW_TOP, true); 596 } 597