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