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