1 /* 2 * Copyright 2006-2012, Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Axel Dörfler, axeld@pinc-software.de 7 * Hugo Santos, hugosantos@gmail.com 8 * Dario Casalinuovo 9 * Rene Gollent, rene@gollent.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_TRANSLATE_CONTEXT 53 #define B_TRANSLATE_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()) 247 SetViewColor(Parent()->ViewColor()); 248 else 249 SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 250 251 SetLowColor(ViewColor()); 252 253 start_watching_network( 254 B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this); 255 256 _Update(); 257 } 258 259 260 void 261 NetworkStatusView::DetachedFromWindow() 262 { 263 stop_watching_network(this); 264 } 265 266 267 void 268 NetworkStatusView::MessageReceived(BMessage* message) 269 { 270 switch (message->what) { 271 case B_NETWORK_MONITOR: 272 _Update(); 273 break; 274 275 case kMsgShowConfiguration: 276 _ShowConfiguration(message); 277 break; 278 279 case kMsgOpenNetworkPreferences: 280 _OpenNetworksPreferences(); 281 break; 282 283 case kMsgJoinNetwork: 284 { 285 const char* deviceName; 286 const char* name; 287 if (message->FindString("device", &deviceName) == B_OK 288 && message->FindString("name", &name) == B_OK) { 289 BNetworkDevice device(deviceName); 290 status_t status = device.JoinNetwork(name); 291 if (status != B_OK) { 292 BString text 293 = B_TRANSLATE("Could not join wireless network:\n"); 294 text << strerror(status); 295 BAlert* alert = new BAlert(name, text.String(), 296 B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, 297 B_STOP_ALERT); 298 alert->Go(NULL); 299 } 300 } 301 break; 302 } 303 304 case B_ABOUT_REQUESTED: 305 _AboutRequested(); 306 break; 307 308 case B_QUIT_REQUESTED: 309 _Quit(); 310 break; 311 312 default: 313 BView::MessageReceived(message); 314 } 315 } 316 317 318 void 319 NetworkStatusView::FrameResized(float width, float height) 320 { 321 _UpdateBitmaps(); 322 Invalidate(); 323 } 324 325 326 void 327 NetworkStatusView::Draw(BRect updateRect) 328 { 329 int32 status = kStatusUnknown; 330 for (std::map<BString, int32>::const_iterator it 331 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 332 if (it->second > status) 333 status = it->second; 334 } 335 336 if (fTrayIcons[status] == NULL) 337 return; 338 339 SetDrawingMode(B_OP_ALPHA); 340 DrawBitmap(fTrayIcons[status]); 341 SetDrawingMode(B_OP_COPY); 342 } 343 344 345 void 346 NetworkStatusView::_ShowConfiguration(BMessage* message) 347 { 348 static const struct information_entry { 349 const char* label; 350 int32 control; 351 } kInformationEntries[] = { 352 { B_TRANSLATE("Address"), SIOCGIFADDR }, 353 { B_TRANSLATE("Broadcast"), SIOCGIFBRDADDR }, 354 { B_TRANSLATE("Netmask"), SIOCGIFNETMASK }, 355 { NULL } 356 }; 357 358 SocketOpener socket; 359 if (socket.InitCheck() != B_OK) 360 return; 361 362 const char* name; 363 if (message->FindString("interface", &name) != B_OK) 364 return; 365 366 ifreq request; 367 if (!_PrepareRequest(request, name)) 368 return; 369 370 BString text(B_TRANSLATE("%ifaceName information:\n")); 371 text.ReplaceFirst("%ifaceName", name); 372 373 size_t boldLength = text.Length(); 374 375 for (int i = 0; kInformationEntries[i].label; i++) { 376 if (ioctl(socket, kInformationEntries[i].control, &request, 377 sizeof(request)) < 0) { 378 continue; 379 } 380 381 char address[32]; 382 sockaddr_in* inetAddress = NULL; 383 switch (kInformationEntries[i].control) { 384 case SIOCGIFNETMASK: 385 inetAddress = (sockaddr_in*)&request.ifr_mask; 386 break; 387 default: 388 inetAddress = (sockaddr_in*)&request.ifr_addr; 389 break; 390 } 391 392 if (inet_ntop(AF_INET, &inetAddress->sin_addr, address, 393 sizeof(address)) == NULL) { 394 return; 395 } 396 397 text << "\n" << kInformationEntries[i].label << ": " << address; 398 } 399 400 BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK")); 401 BTextView* view = alert->TextView(); 402 BFont font; 403 404 view->SetStylable(true); 405 view->GetFont(&font); 406 font.SetFace(B_BOLD_FACE); 407 view->SetFontAndColor(0, boldLength, &font); 408 409 alert->Go(NULL); 410 } 411 412 413 void 414 NetworkStatusView::MouseDown(BPoint point) 415 { 416 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 417 menu->SetAsyncAutoDestruct(true); 418 menu->SetFont(be_plain_font); 419 BString wifiInterface; 420 BNetworkDevice wifiDevice; 421 422 // Add interfaces 423 424 for (std::map<BString, int32>::const_iterator it 425 = fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) { 426 const BString& name = it->first; 427 428 BString label = name; 429 label += ": "; 430 label += kStatusDescriptions[ 431 _DetermineInterfaceStatus(name.String())]; 432 433 BMessage* info = new BMessage(kMsgShowConfiguration); 434 info->AddString("interface", name.String()); 435 menu->AddItem(new BMenuItem(label.String(), info)); 436 437 // We only show the networks of the first wireless device we find. 438 if (wifiInterface.IsEmpty()) { 439 wifiDevice.SetTo(name); 440 if (wifiDevice.IsWireless()) 441 wifiInterface = name; 442 } 443 } 444 445 if (!fInterfaceStatuses.empty()) 446 menu->AddSeparatorItem(); 447 448 // Add wireless networks, if any 449 450 if (!wifiInterface.IsEmpty()) { 451 std::set<BNetworkAddress> associated; 452 BNetworkAddress address; 453 uint32 cookie = 0; 454 while (wifiDevice.GetNextAssociatedNetwork(cookie, address) == B_OK) 455 associated.insert(address); 456 457 wireless_network network; 458 int32 count = 0; 459 cookie = 0; 460 while (wifiDevice.GetNextNetwork(cookie, network) == B_OK) { 461 BMessage* message = new BMessage(kMsgJoinNetwork); 462 message->AddString("device", wifiInterface); 463 message->AddString("name", network.name); 464 465 BMenuItem* item = new WirelessNetworkMenuItem(network.name, 466 network.signal_strength, 467 (network.flags & B_NETWORK_IS_ENCRYPTED) != 0, message); 468 menu->AddItem(item); 469 if (associated.find(network.address) != associated.end()) 470 item->SetMarked(true); 471 472 count++; 473 } 474 if (count == 0) { 475 BMenuItem* item = new BMenuItem( 476 B_TRANSLATE("<no wireless networks found>"), NULL); 477 item->SetEnabled(false); 478 menu->AddItem(item); 479 } 480 menu->AddSeparatorItem(); 481 } 482 483 menu->AddItem(new BMenuItem(B_TRANSLATE( 484 "Open network preferences" B_UTF8_ELLIPSIS), 485 new BMessage(kMsgOpenNetworkPreferences))); 486 487 if (fInDeskbar) { 488 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 489 new BMessage(B_QUIT_REQUESTED))); 490 } 491 menu->SetTargetForItems(this); 492 493 ConvertToScreen(&point); 494 menu->Go(point, true, true, true); 495 } 496 497 498 void 499 NetworkStatusView::_AboutRequested() 500 { 501 BString about = B_TRANSLATE( 502 "NetworkStatus\n\twritten by %1 and Hugo Santos\n\t%2, Haiku, Inc.\n" 503 ); 504 about.ReplaceFirst("%1", "Axel Dörfler"); 505 // Append a new developer here 506 about.ReplaceFirst("%2", "Copyright 2007-2010"); 507 // Append a new year here 508 BAlert* alert = new BAlert("about", about, B_TRANSLATE("OK")); 509 BTextView *view = alert->TextView(); 510 BFont font; 511 512 view->SetStylable(true); 513 514 view->GetFont(&font); 515 font.SetSize(18); 516 font.SetFace(B_BOLD_FACE); 517 view->SetFontAndColor(0, 13, &font); 518 519 alert->Go(); 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 603 // asynchronous alert in order to not block replicant host application 604 alert->Go(NULL); 605 } 606 } 607 608 609 // #pragma mark - 610 611 612 extern "C" _EXPORT BView * 613 instantiate_deskbar_item(void) 614 { 615 return new NetworkStatusView(BRect(0, 0, 15, 15), 616 B_FOLLOW_LEFT | B_FOLLOW_TOP, true); 617 } 618 619