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