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->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 302 alert->Go(NULL); 303 } 304 } 305 break; 306 } 307 308 case B_ABOUT_REQUESTED: 309 _AboutRequested(); 310 break; 311 312 case B_QUIT_REQUESTED: 313 _Quit(); 314 break; 315 316 default: 317 BView::MessageReceived(message); 318 } 319 } 320 321 322 void 323 NetworkStatusView::FrameResized(float width, float height) 324 { 325 _UpdateBitmaps(); 326 Invalidate(); 327 } 328 329 330 void 331 NetworkStatusView::Draw(BRect updateRect) 332 { 333 int32 status = kStatusUnknown; 334 for (std::map<BString, int32>::const_iterator it 335 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 336 if (it->second > status) 337 status = it->second; 338 } 339 340 if (fTrayIcons[status] == NULL) 341 return; 342 343 SetDrawingMode(B_OP_ALPHA); 344 DrawBitmap(fTrayIcons[status]); 345 SetDrawingMode(B_OP_COPY); 346 } 347 348 349 void 350 NetworkStatusView::_ShowConfiguration(BMessage* message) 351 { 352 static const struct information_entry { 353 const char* label; 354 int32 control; 355 } kInformationEntries[] = { 356 { B_TRANSLATE("Address"), SIOCGIFADDR }, 357 { B_TRANSLATE("Broadcast"), SIOCGIFBRDADDR }, 358 { B_TRANSLATE("Netmask"), SIOCGIFNETMASK }, 359 { NULL } 360 }; 361 362 SocketOpener socket; 363 if (socket.InitCheck() != B_OK) 364 return; 365 366 const char* name; 367 if (message->FindString("interface", &name) != B_OK) 368 return; 369 370 ifreq request; 371 if (!_PrepareRequest(request, name)) 372 return; 373 374 BString text(B_TRANSLATE("%ifaceName information:\n")); 375 text.ReplaceFirst("%ifaceName", name); 376 377 size_t boldLength = text.Length(); 378 379 for (int i = 0; kInformationEntries[i].label; i++) { 380 if (ioctl(socket, kInformationEntries[i].control, &request, 381 sizeof(request)) < 0) { 382 continue; 383 } 384 385 char address[32]; 386 sockaddr_in* inetAddress = NULL; 387 switch (kInformationEntries[i].control) { 388 case SIOCGIFNETMASK: 389 inetAddress = (sockaddr_in*)&request.ifr_mask; 390 break; 391 default: 392 inetAddress = (sockaddr_in*)&request.ifr_addr; 393 break; 394 } 395 396 if (inet_ntop(AF_INET, &inetAddress->sin_addr, address, 397 sizeof(address)) == NULL) { 398 return; 399 } 400 401 text << "\n" << kInformationEntries[i].label << ": " << address; 402 } 403 404 BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK")); 405 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 406 BTextView* view = alert->TextView(); 407 BFont font; 408 409 view->SetStylable(true); 410 view->GetFont(&font); 411 font.SetFace(B_BOLD_FACE); 412 view->SetFontAndColor(0, boldLength, &font); 413 414 alert->Go(NULL); 415 } 416 417 418 void 419 NetworkStatusView::MouseDown(BPoint point) 420 { 421 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 422 menu->SetAsyncAutoDestruct(true); 423 menu->SetFont(be_plain_font); 424 BString wifiInterface; 425 BNetworkDevice wifiDevice; 426 427 // Add interfaces 428 429 for (std::map<BString, int32>::const_iterator it 430 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 431 const BString& name = it->first; 432 433 BString label = name; 434 label += ": "; 435 label += kStatusDescriptions[ 436 _DetermineInterfaceStatus(name.String())]; 437 438 BMessage* info = new BMessage(kMsgShowConfiguration); 439 info->AddString("interface", name.String()); 440 menu->AddItem(new BMenuItem(label.String(), info)); 441 442 // We only show the networks of the first wireless device we find. 443 if (wifiInterface.IsEmpty()) { 444 wifiDevice.SetTo(name); 445 if (wifiDevice.IsWireless()) 446 wifiInterface = name; 447 } 448 } 449 450 if (!fInterfaceStatuses.empty()) 451 menu->AddSeparatorItem(); 452 453 // Add wireless networks, if any 454 455 if (!wifiInterface.IsEmpty()) { 456 std::set<BNetworkAddress> associated; 457 BNetworkAddress address; 458 uint32 cookie = 0; 459 while (wifiDevice.GetNextAssociatedNetwork(cookie, address) == B_OK) 460 associated.insert(address); 461 462 wireless_network network; 463 int32 count = 0; 464 cookie = 0; 465 while (wifiDevice.GetNextNetwork(cookie, network) == B_OK) { 466 BMessage* message = new BMessage(kMsgJoinNetwork); 467 message->AddString("device", wifiInterface); 468 message->AddString("name", network.name); 469 470 BMenuItem* item = new WirelessNetworkMenuItem(network.name, 471 network.signal_strength, 472 (network.flags & B_NETWORK_IS_ENCRYPTED) != 0, message); 473 menu->AddItem(item); 474 if (associated.find(network.address) != associated.end()) 475 item->SetMarked(true); 476 477 count++; 478 } 479 if (count == 0) { 480 BMenuItem* item = new BMenuItem( 481 B_TRANSLATE("<no wireless networks found>"), NULL); 482 item->SetEnabled(false); 483 menu->AddItem(item); 484 } 485 menu->AddSeparatorItem(); 486 } 487 488 menu->AddItem(new BMenuItem(B_TRANSLATE( 489 "Open network preferences" B_UTF8_ELLIPSIS), 490 new BMessage(kMsgOpenNetworkPreferences))); 491 492 if (fInDeskbar) { 493 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 494 new BMessage(B_QUIT_REQUESTED))); 495 } 496 menu->SetTargetForItems(this); 497 498 ConvertToScreen(&point); 499 menu->Go(point, true, true, true); 500 } 501 502 503 void 504 NetworkStatusView::_AboutRequested() 505 { 506 BString about = B_TRANSLATE( 507 "NetworkStatus\n\twritten by %1 and Hugo Santos\n\t%2, Haiku, Inc.\n" 508 ); 509 about.ReplaceFirst("%1", "Axel Dörfler"); 510 // Append a new developer here 511 about.ReplaceFirst("%2", "Copyright 2007-2010"); 512 // Append a new year here 513 BAlert* alert = new BAlert("about", about, B_TRANSLATE("OK")); 514 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 515 BTextView *view = alert->TextView(); 516 BFont font; 517 518 view->SetStylable(true); 519 520 view->GetFont(&font); 521 font.SetSize(18); 522 font.SetFace(B_BOLD_FACE); 523 view->SetFontAndColor(0, 13, &font); 524 525 alert->Go(); 526 } 527 528 529 bool 530 NetworkStatusView::_PrepareRequest(struct ifreq& request, const char* name) 531 { 532 if (strlen(name) > IF_NAMESIZE) 533 return false; 534 535 strcpy(request.ifr_name, name); 536 return true; 537 } 538 539 540 int32 541 NetworkStatusView::_DetermineInterfaceStatus( 542 const BNetworkInterface& interface) 543 { 544 uint32 flags = interface.Flags(); 545 int32 status = kStatusNoLink; 546 547 // TODO: no kStatusLinkNoConfig yet 548 549 if (flags & IFF_CONFIGURING) 550 status = kStatusConnecting; 551 else if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) 552 status = kStatusReady; 553 554 return status; 555 } 556 557 558 void 559 NetworkStatusView::_Update(bool force) 560 { 561 BNetworkRoster& roster = BNetworkRoster::Default(); 562 BNetworkInterface interface; 563 uint32 cookie = 0; 564 565 while (roster.GetNextInterface(&cookie, interface) == B_OK) { 566 if ((interface.Flags() & IFF_LOOPBACK) == 0) { 567 int32 oldStatus = kStatusUnknown; 568 if (fInterfaceStatuses.find(interface.Name()) 569 != fInterfaceStatuses.end()) { 570 oldStatus = fInterfaceStatuses[interface.Name()]; 571 } 572 int32 status = _DetermineInterfaceStatus(interface); 573 if (oldStatus != status) { 574 BNotification notification(B_INFORMATION_NOTIFICATION); 575 notification.SetGroup(B_TRANSLATE("Network Status")); 576 notification.SetTitle(interface.Name()); 577 notification.SetMessageID(interface.Name()); 578 notification.SetIcon(fNotifyIcons[status]); 579 if (status == kStatusConnecting 580 || (status == kStatusReady 581 && oldStatus == kStatusConnecting) 582 || (status == kStatusNoLink 583 && oldStatus == kStatusReady) 584 || (status == kStatusNoLink 585 && oldStatus == kStatusConnecting)) { 586 // A significant state change, raise notification. 587 notification.SetContent(kStatusDescriptions[status]); 588 notification.Send(); 589 } 590 Invalidate(); 591 } 592 fInterfaceStatuses[interface.Name()] = status; 593 } 594 } 595 } 596 597 598 void 599 NetworkStatusView::_OpenNetworksPreferences() 600 { 601 status_t status = be_roster->Launch("application/x-vnd.Haiku-Network"); 602 if (status != B_OK && status != B_ALREADY_RUNNING) { 603 BString errorMessage(B_TRANSLATE("Launching the network preflet " 604 "failed.\n\nError: ")); 605 errorMessage << strerror(status); 606 BAlert* alert = new BAlert("launch error", errorMessage.String(), 607 B_TRANSLATE("OK")); 608 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 609 610 // asynchronous alert in order to not block replicant host application 611 alert->Go(NULL); 612 } 613 } 614 615 616 // #pragma mark - 617 618 619 extern "C" _EXPORT BView * 620 instantiate_deskbar_item(void) 621 { 622 return new NetworkStatusView(BRect(0, 0, 15, 15), 623 B_FOLLOW_LEFT | B_FOLLOW_TOP, true); 624 } 625 626