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 } 143 144 145 void 146 NetworkStatusView::_Init() 147 { 148 for (int i = 0; i < kStatusCount; i++) { 149 fTrayIcons[i] = NULL; 150 fNotifyIcons[i] = NULL; 151 } 152 153 _UpdateBitmaps(); 154 } 155 156 157 void 158 NetworkStatusView::_UpdateBitmaps() 159 { 160 for (int i = 0; i < kStatusCount; i++) { 161 delete fTrayIcons[i]; 162 delete fNotifyIcons[i]; 163 fTrayIcons[i] = NULL; 164 fNotifyIcons[i] = NULL; 165 } 166 167 image_info info; 168 if (our_image(info) != B_OK) 169 return; 170 171 BFile file(info.name, B_READ_ONLY); 172 if (file.InitCheck() < B_OK) 173 return; 174 175 BResources resources(&file); 176 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 177 if (resources.InitCheck() < B_OK) 178 return; 179 #endif 180 181 for (int i = 0; i < kStatusCount; i++) { 182 const void* data = NULL; 183 size_t size; 184 data = resources.LoadResource(B_VECTOR_ICON_TYPE, 185 kNetworkStatusNoDevice + i, &size); 186 if (data != NULL) { 187 // Scale main tray icon 188 BBitmap* trayIcon = new BBitmap(Bounds(), B_RGBA32); 189 if (trayIcon->InitCheck() == B_OK 190 && BIconUtils::GetVectorIcon((const uint8 *)data, 191 size, trayIcon) == B_OK) { 192 fTrayIcons[i] = trayIcon; 193 } else 194 delete trayIcon; 195 196 // Scale notification icon 197 BBitmap* notifyIcon = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32); 198 if (notifyIcon->InitCheck() == B_OK 199 && BIconUtils::GetVectorIcon((const uint8 *)data, 200 size, notifyIcon) == B_OK) { 201 fNotifyIcons[i] = notifyIcon; 202 } else 203 delete notifyIcon; 204 } 205 } 206 } 207 208 209 void 210 NetworkStatusView::_Quit() 211 { 212 if (fInDeskbar) { 213 BDeskbar deskbar; 214 deskbar.RemoveItem(kDeskbarItemName); 215 } else 216 be_app->PostMessage(B_QUIT_REQUESTED); 217 } 218 219 220 NetworkStatusView* 221 NetworkStatusView::Instantiate(BMessage* archive) 222 { 223 if (!validate_instantiation(archive, "NetworkStatusView")) 224 return NULL; 225 226 return new NetworkStatusView(archive); 227 } 228 229 230 status_t 231 NetworkStatusView::Archive(BMessage* archive, bool deep) const 232 { 233 status_t status = BView::Archive(archive, deep); 234 if (status == B_OK) 235 status = archive->AddString("add_on", kSignature); 236 if (status == B_OK) 237 status = archive->AddString("class", "NetworkStatusView"); 238 239 return status; 240 } 241 242 243 void 244 NetworkStatusView::AttachedToWindow() 245 { 246 BView::AttachedToWindow(); 247 if (Parent() != NULL) { 248 if ((Parent()->Flags() & B_DRAW_ON_CHILDREN) != 0) 249 SetViewColor(B_TRANSPARENT_COLOR); 250 else 251 SetViewColor(Parent()->ViewColor()); 252 } else 253 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 254 255 SetLowColor(ViewColor()); 256 257 start_watching_network( 258 B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this); 259 260 _Update(); 261 } 262 263 264 void 265 NetworkStatusView::DetachedFromWindow() 266 { 267 stop_watching_network(this); 268 } 269 270 271 void 272 NetworkStatusView::MessageReceived(BMessage* message) 273 { 274 switch (message->what) { 275 case B_NETWORK_MONITOR: 276 _Update(); 277 break; 278 279 case kMsgShowConfiguration: 280 _ShowConfiguration(message); 281 break; 282 283 case kMsgOpenNetworkPreferences: 284 _OpenNetworksPreferences(); 285 break; 286 287 case kMsgJoinNetwork: 288 { 289 const char* deviceName; 290 const char* name; 291 if (message->FindString("device", &deviceName) == B_OK 292 && message->FindString("name", &name) == B_OK) { 293 BNetworkDevice device(deviceName); 294 status_t status = device.JoinNetwork(name); 295 if (status != B_OK) { 296 BString text 297 = B_TRANSLATE("Could not join wireless network:\n"); 298 text << strerror(status); 299 BAlert* alert = new BAlert(name, text.String(), 300 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 301 B_STOP_ALERT); 302 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 303 alert->Go(NULL); 304 } 305 } 306 break; 307 } 308 309 case B_ABOUT_REQUESTED: 310 _AboutRequested(); 311 break; 312 313 case B_QUIT_REQUESTED: 314 _Quit(); 315 break; 316 317 default: 318 BView::MessageReceived(message); 319 } 320 } 321 322 323 void 324 NetworkStatusView::FrameResized(float width, float height) 325 { 326 _UpdateBitmaps(); 327 Invalidate(); 328 } 329 330 331 void 332 NetworkStatusView::Draw(BRect updateRect) 333 { 334 int32 status = kStatusUnknown; 335 for (std::map<BString, int32>::const_iterator it 336 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 337 if (it->second > status) 338 status = it->second; 339 } 340 341 if (fTrayIcons[status] == NULL) 342 return; 343 344 SetDrawingMode(B_OP_ALPHA); 345 DrawBitmap(fTrayIcons[status]); 346 SetDrawingMode(B_OP_COPY); 347 } 348 349 350 void 351 NetworkStatusView::_ShowConfiguration(BMessage* message) 352 { 353 static const struct information_entry { 354 const char* label; 355 int32 control; 356 } kInformationEntries[] = { 357 { B_TRANSLATE("Address"), SIOCGIFADDR }, 358 { B_TRANSLATE("Broadcast"), SIOCGIFBRDADDR }, 359 { B_TRANSLATE("Netmask"), SIOCGIFNETMASK }, 360 { NULL } 361 }; 362 363 SocketOpener socket; 364 if (socket.InitCheck() != B_OK) 365 return; 366 367 const char* name; 368 if (message->FindString("interface", &name) != B_OK) 369 return; 370 371 ifreq request; 372 if (!_PrepareRequest(request, name)) 373 return; 374 375 BString text(B_TRANSLATE("%ifaceName information:\n")); 376 text.ReplaceFirst("%ifaceName", name); 377 378 size_t boldLength = text.Length(); 379 380 for (int i = 0; kInformationEntries[i].label; i++) { 381 if (ioctl(socket, kInformationEntries[i].control, &request, 382 sizeof(request)) < 0) { 383 continue; 384 } 385 386 char address[32]; 387 sockaddr_in* inetAddress = NULL; 388 switch (kInformationEntries[i].control) { 389 case SIOCGIFNETMASK: 390 inetAddress = (sockaddr_in*)&request.ifr_mask; 391 break; 392 default: 393 inetAddress = (sockaddr_in*)&request.ifr_addr; 394 break; 395 } 396 397 if (inet_ntop(AF_INET, &inetAddress->sin_addr, address, 398 sizeof(address)) == NULL) { 399 return; 400 } 401 402 text << "\n" << kInformationEntries[i].label << ": " << address; 403 } 404 405 BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK")); 406 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 407 BTextView* view = alert->TextView(); 408 BFont font; 409 410 view->SetStylable(true); 411 view->GetFont(&font); 412 font.SetFace(B_BOLD_FACE); 413 view->SetFontAndColor(0, boldLength, &font); 414 415 alert->Go(NULL); 416 } 417 418 419 void 420 NetworkStatusView::MouseDown(BPoint point) 421 { 422 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 423 menu->SetAsyncAutoDestruct(true); 424 menu->SetFont(be_plain_font); 425 BString wifiInterface; 426 BNetworkDevice wifiDevice; 427 428 // Add interfaces 429 430 for (std::map<BString, int32>::const_iterator it 431 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 432 const BString& name = it->first; 433 434 BString label = name; 435 label += ": "; 436 label += kStatusDescriptions[ 437 _DetermineInterfaceStatus(name.String())]; 438 439 BMessage* info = new BMessage(kMsgShowConfiguration); 440 info->AddString("interface", name.String()); 441 menu->AddItem(new BMenuItem(label.String(), info)); 442 443 // We only show the networks of the first wireless device we find. 444 if (wifiInterface.IsEmpty()) { 445 wifiDevice.SetTo(name); 446 if (wifiDevice.IsWireless()) 447 wifiInterface = name; 448 } 449 } 450 451 if (!fInterfaceStatuses.empty()) 452 menu->AddSeparatorItem(); 453 454 // Add wireless networks, if any 455 456 if (!wifiInterface.IsEmpty()) { 457 std::set<BNetworkAddress> associated; 458 BNetworkAddress address; 459 uint32 cookie = 0; 460 while (wifiDevice.GetNextAssociatedNetwork(cookie, address) == B_OK) 461 associated.insert(address); 462 463 wireless_network network; 464 int32 count = 0; 465 cookie = 0; 466 while (wifiDevice.GetNextNetwork(cookie, network) == B_OK) { 467 BMessage* message = new BMessage(kMsgJoinNetwork); 468 message->AddString("device", wifiInterface); 469 message->AddString("name", network.name); 470 471 BMenuItem* item = new WirelessNetworkMenuItem(network.name, 472 network.signal_strength, 473 (network.flags & B_NETWORK_IS_ENCRYPTED) != 0, message); 474 menu->AddItem(item); 475 if (associated.find(network.address) != associated.end()) 476 item->SetMarked(true); 477 478 count++; 479 } 480 if (count == 0) { 481 BMenuItem* item = new BMenuItem( 482 B_TRANSLATE("<no wireless networks found>"), NULL); 483 item->SetEnabled(false); 484 menu->AddItem(item); 485 } 486 menu->AddSeparatorItem(); 487 } 488 489 menu->AddItem(new BMenuItem(B_TRANSLATE( 490 "Open network preferences" B_UTF8_ELLIPSIS), 491 new BMessage(kMsgOpenNetworkPreferences))); 492 493 if (fInDeskbar) { 494 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 495 new BMessage(B_QUIT_REQUESTED))); 496 } 497 menu->SetTargetForItems(this); 498 499 ConvertToScreen(&point); 500 menu->Go(point, true, true, true); 501 } 502 503 504 void 505 NetworkStatusView::_AboutRequested() 506 { 507 BAboutWindow* window = new BAboutWindow( 508 B_TRANSLATE_SYSTEM_NAME("NetworkStatus"), kSignature); 509 510 const char* authors[] = { 511 "Axel Dörfler", 512 "Hugo Santos", 513 NULL 514 }; 515 516 window->AddCopyright(2007, "Haiku, Inc."); 517 window->AddAuthors(authors); 518 519 window->Show(); 520 } 521 522 523 bool 524 NetworkStatusView::_PrepareRequest(struct ifreq& request, const char* name) 525 { 526 if (strlen(name) > IF_NAMESIZE) 527 return false; 528 529 strcpy(request.ifr_name, name); 530 return true; 531 } 532 533 534 int32 535 NetworkStatusView::_DetermineInterfaceStatus( 536 const BNetworkInterface& interface) 537 { 538 uint32 flags = interface.Flags(); 539 int32 status = kStatusNoLink; 540 541 // TODO: no kStatusLinkNoConfig yet 542 543 if (flags & IFF_CONFIGURING) 544 status = kStatusConnecting; 545 else if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) 546 status = kStatusReady; 547 548 return status; 549 } 550 551 552 void 553 NetworkStatusView::_Update(bool force) 554 { 555 BNetworkRoster& roster = BNetworkRoster::Default(); 556 BNetworkInterface interface; 557 uint32 cookie = 0; 558 559 while (roster.GetNextInterface(&cookie, interface) == B_OK) { 560 if ((interface.Flags() & IFF_LOOPBACK) == 0) { 561 int32 oldStatus = kStatusUnknown; 562 if (fInterfaceStatuses.find(interface.Name()) 563 != fInterfaceStatuses.end()) { 564 oldStatus = fInterfaceStatuses[interface.Name()]; 565 } 566 int32 status = _DetermineInterfaceStatus(interface); 567 if (oldStatus != status) { 568 BNotification notification(B_INFORMATION_NOTIFICATION); 569 notification.SetGroup(B_TRANSLATE("Network Status")); 570 notification.SetTitle(interface.Name()); 571 notification.SetMessageID(interface.Name()); 572 notification.SetIcon(fNotifyIcons[status]); 573 if (status == kStatusConnecting 574 || (status == kStatusReady 575 && oldStatus == kStatusConnecting) 576 || (status == kStatusNoLink 577 && oldStatus == kStatusReady) 578 || (status == kStatusNoLink 579 && oldStatus == kStatusConnecting)) { 580 // A significant state change, raise notification. 581 notification.SetContent(kStatusDescriptions[status]); 582 notification.Send(); 583 } 584 Invalidate(); 585 } 586 fInterfaceStatuses[interface.Name()] = status; 587 } 588 } 589 } 590 591 592 void 593 NetworkStatusView::_OpenNetworksPreferences() 594 { 595 status_t status = be_roster->Launch("application/x-vnd.Haiku-Network"); 596 if (status != B_OK && status != B_ALREADY_RUNNING) { 597 BString errorMessage(B_TRANSLATE("Launching the network preflet " 598 "failed.\n\nError: ")); 599 errorMessage << strerror(status); 600 BAlert* alert = new BAlert("launch error", errorMessage.String(), 601 B_TRANSLATE("OK")); 602 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 603 604 // asynchronous alert in order to not block replicant host application 605 alert->Go(NULL); 606 } 607 } 608 609 610 // #pragma mark - 611 612 613 extern "C" _EXPORT BView * 614 instantiate_deskbar_item(void) 615 { 616 return new NetworkStatusView(BRect(0, 0, 15, 15), 617 B_FOLLOW_LEFT | B_FOLLOW_TOP, true); 618 } 619 620