1 /* 2 * Copyright 2006-2009, 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 */ 10 11 12 #include "NetworkStatusView.h" 13 14 #include <set> 15 16 #include <arpa/inet.h> 17 #include <net/if.h> 18 #include <stdio.h> 19 #include <stdlib.h> 20 #include <string.h> 21 #include <sys/socket.h> 22 #include <sys/sockio.h> 23 #include <unistd.h> 24 25 #include <Alert.h> 26 #include <Application.h> 27 #include <Catalog.h> 28 #include <Bitmap.h> 29 #include <Deskbar.h> 30 #include <Dragger.h> 31 #include <Drivers.h> 32 #include <IconUtils.h> 33 #include <Locale.h> 34 #include <MenuItem.h> 35 #include <MessageRunner.h> 36 #include <NetworkDevice.h> 37 #include <NetworkInterface.h> 38 #include <NetworkRoster.h> 39 #include <PopUpMenu.h> 40 #include <Resources.h> 41 #include <Roster.h> 42 #include <String.h> 43 #include <TextView.h> 44 45 #include "NetworkStatus.h" 46 #include "NetworkStatusIcons.h" 47 #include "RadioView.h" 48 #include "WirelessNetworkMenuItem.h" 49 50 51 #undef B_TRANSLATE_CONTEXT 52 #define B_TRANSLATE_CONTEXT "NetworkStatusView" 53 54 55 static const char *kStatusDescriptions[] = { 56 B_TRANSLATE("Unknown"), 57 B_TRANSLATE("No link"), 58 B_TRANSLATE("No stateful configuration"), 59 B_TRANSLATE("Configuring"), 60 B_TRANSLATE("Ready") 61 }; 62 63 extern "C" _EXPORT BView *instantiate_deskbar_item(void); 64 65 66 const uint32 kMsgShowConfiguration = 'shcf'; 67 const uint32 kMsgOpenNetworkPreferences = 'onwp'; 68 const uint32 kMsgJoinNetwork = 'join'; 69 70 const uint32 kMinIconWidth = 16; 71 const uint32 kMinIconHeight = 16; 72 73 74 class SocketOpener { 75 public: 76 SocketOpener() 77 { 78 fSocket = socket(AF_INET, SOCK_DGRAM, 0); 79 } 80 81 ~SocketOpener() 82 { 83 close(fSocket); 84 } 85 86 status_t InitCheck() 87 { 88 return fSocket >= 0 ? B_OK : B_ERROR; 89 } 90 91 operator int() const 92 { 93 return fSocket; 94 } 95 96 private: 97 int fSocket; 98 }; 99 100 101 // #pragma mark - 102 103 104 NetworkStatusView::NetworkStatusView(BRect frame, int32 resizingMode, 105 bool inDeskbar) 106 : BView(frame, kDeskbarItemName, resizingMode, 107 B_WILL_DRAW | B_FRAME_EVENTS), 108 fInDeskbar(inDeskbar), 109 fStatus(kStatusUnknown) 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 if (fTrayIcons[fStatus] == NULL) 330 return; 331 332 SetDrawingMode(B_OP_ALPHA); 333 DrawBitmap(fTrayIcons[fStatus]); 334 SetDrawingMode(B_OP_COPY); 335 } 336 337 338 void 339 NetworkStatusView::_ShowConfiguration(BMessage* message) 340 { 341 static const struct information_entry { 342 const char* label; 343 int32 control; 344 } kInformationEntries[] = { 345 { B_TRANSLATE("Address"), SIOCGIFADDR }, 346 { B_TRANSLATE("Broadcast"), SIOCGIFBRDADDR }, 347 { B_TRANSLATE("Netmask"), SIOCGIFNETMASK }, 348 { NULL } 349 }; 350 351 SocketOpener socket; 352 if (socket.InitCheck() != B_OK) 353 return; 354 355 const char* name; 356 if (message->FindString("interface", &name) != B_OK) 357 return; 358 359 ifreq request; 360 if (!_PrepareRequest(request, name)) 361 return; 362 363 BString text(B_TRANSLATE("%ifaceName information:\n")); 364 text.ReplaceFirst("%ifaceName", name); 365 366 size_t boldLength = text.Length(); 367 368 for (int i = 0; kInformationEntries[i].label; i++) { 369 if (ioctl(socket, kInformationEntries[i].control, &request, 370 sizeof(request)) < 0) { 371 continue; 372 } 373 374 char address[32]; 375 sockaddr_in* inetAddress = NULL; 376 switch (kInformationEntries[i].control) { 377 case SIOCGIFNETMASK: 378 inetAddress = (sockaddr_in*)&request.ifr_mask; 379 break; 380 default: 381 inetAddress = (sockaddr_in*)&request.ifr_addr; 382 break; 383 } 384 385 if (inet_ntop(AF_INET, &inetAddress->sin_addr, address, 386 sizeof(address)) == NULL) { 387 return; 388 } 389 390 text << "\n" << kInformationEntries[i].label << ": " << address; 391 } 392 393 BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK")); 394 BTextView* view = alert->TextView(); 395 BFont font; 396 397 view->SetStylable(true); 398 view->GetFont(&font); 399 font.SetFace(B_BOLD_FACE); 400 view->SetFontAndColor(0, boldLength, &font); 401 402 alert->Go(NULL); 403 } 404 405 406 void 407 NetworkStatusView::MouseDown(BPoint point) 408 { 409 BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false); 410 menu->SetAsyncAutoDestruct(true); 411 menu->SetFont(be_plain_font); 412 413 // Add interfaces 414 415 for (int32 i = 0; i < fInterfaces.CountItems(); i++) { 416 BString& name = *fInterfaces.ItemAt(i); 417 418 BString label = name; 419 label += ": "; 420 label += kStatusDescriptions[ 421 _DetermineInterfaceStatus(name.String())]; 422 423 BMessage* info = new BMessage(kMsgShowConfiguration); 424 info->AddString("interface", name.String()); 425 menu->AddItem(new BMenuItem(label.String(), info)); 426 } 427 428 if (!fInterfaces.IsEmpty()) 429 menu->AddSeparatorItem(); 430 431 // Add wireless networks, if any 432 433 for (int32 i = 0; i < fInterfaces.CountItems(); i++) { 434 BNetworkDevice device(fInterfaces.ItemAt(i)->String()); 435 if (!device.IsWireless()) 436 continue; 437 438 std::set<BNetworkAddress> associated; 439 BNetworkAddress address; 440 uint32 cookie = 0; 441 while (device.GetNextAssociatedNetwork(cookie, address) == B_OK) 442 associated.insert(address); 443 444 wireless_network network; 445 int32 count = 0; 446 cookie = 0; 447 while (device.GetNextNetwork(cookie, network) == B_OK) { 448 BMessage* message = new BMessage(kMsgJoinNetwork); 449 message->AddString("device", device.Name()); 450 message->AddString("name", network.name); 451 452 BMenuItem* item = new WirelessNetworkMenuItem(network.name, 453 network.signal_strength, 454 (network.flags & B_NETWORK_IS_ENCRYPTED) != 0, message); 455 menu->AddItem(item); 456 if (associated.find(network.address) != associated.end()) 457 item->SetMarked(true); 458 459 count++; 460 } 461 if (count == 0) { 462 BMenuItem* item = new BMenuItem( 463 B_TRANSLATE("<no wireless networks found>"), NULL); 464 item->SetEnabled(false); 465 menu->AddItem(item); 466 } 467 menu->AddSeparatorItem(); 468 469 // We only show the networks of the first wireless device we find. 470 break; 471 } 472 473 menu->AddItem(new BMenuItem(B_TRANSLATE( 474 "Open network preferences" B_UTF8_ELLIPSIS), 475 new BMessage(kMsgOpenNetworkPreferences))); 476 477 if (fInDeskbar) { 478 menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"), 479 new BMessage(B_QUIT_REQUESTED))); 480 } 481 menu->SetTargetForItems(this); 482 483 ConvertToScreen(&point); 484 menu->Go(point, true, true, true); 485 } 486 487 488 void 489 NetworkStatusView::_AboutRequested() 490 { 491 BString about = B_TRANSLATE( 492 "NetworkStatus\n\twritten by %1 and Hugo Santos\n\t%2, Haiku, Inc.\n" 493 ); 494 about.ReplaceFirst("%1", "Axel Dörfler"); 495 // Append a new developer here 496 about.ReplaceFirst("%2", "Copyright 2007-2010"); 497 // Append a new year here 498 BAlert* alert = new BAlert("about", about, B_TRANSLATE("OK")); 499 BTextView *view = alert->TextView(); 500 BFont font; 501 502 view->SetStylable(true); 503 504 view->GetFont(&font); 505 font.SetSize(18); 506 font.SetFace(B_BOLD_FACE); 507 view->SetFontAndColor(0, 13, &font); 508 509 alert->Go(); 510 } 511 512 513 bool 514 NetworkStatusView::_PrepareRequest(struct ifreq& request, const char* name) 515 { 516 if (strlen(name) > IF_NAMESIZE) 517 return false; 518 519 strcpy(request.ifr_name, name); 520 return true; 521 } 522 523 524 int32 525 NetworkStatusView::_DetermineInterfaceStatus(const char* name) 526 { 527 BNetworkInterface interface(name); 528 uint32 flags = interface.Flags(); 529 int32 status = kStatusNoLink; 530 531 // TODO: no kStatusLinkNoConfig yet 532 533 if (flags & IFF_CONFIGURING) 534 status = kStatusConnecting; 535 else if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK)) 536 status = kStatusReady; 537 538 return status; 539 } 540 541 542 void 543 NetworkStatusView::_Update(bool force) 544 { 545 int32 oldStatus = fStatus; 546 fStatus = kStatusUnknown; 547 fInterfaces.MakeEmpty(); 548 549 BNetworkRoster& roster = BNetworkRoster::Default(); 550 BNetworkInterface interface; 551 uint32 cookie = 0; 552 553 while (roster.GetNextInterface(&cookie, interface) == B_OK) { 554 if ((interface.Flags() & IFF_LOOPBACK) == 0) { 555 fInterfaces.AddItem(new BString(interface.Name())); 556 int32 status = _DetermineInterfaceStatus(interface.Name()); 557 if (status > fStatus) 558 fStatus = status; 559 } 560 } 561 562 if (fStatus != oldStatus) { 563 // A little notification on major status changes for primary interface 564 BNotification notification(B_INFORMATION_NOTIFICATION); 565 notification.SetGroup(B_TRANSLATE("Network Status")); 566 notification.SetTitle(interface.Name()); 567 notification.SetMessageID(interface.Name()); 568 notification.SetIcon(fNotifyIcons[fStatus]); 569 if (fStatus == kStatusConnecting 570 || (fStatus == kStatusReady && oldStatus == kStatusConnecting) 571 || (fStatus == kStatusNoLink && oldStatus == kStatusReady) 572 || (fStatus == kStatusNoLink && oldStatus == kStatusConnecting)) { 573 // A significant state change, raise notification. 574 notification.SetContent(kStatusDescriptions[fStatus]); 575 notification.Send(); 576 } 577 578 Invalidate(); 579 } 580 } 581 582 583 void 584 NetworkStatusView::_OpenNetworksPreferences() 585 { 586 status_t status = be_roster->Launch("application/x-vnd.Haiku-Network"); 587 if (status != B_OK && status != B_ALREADY_RUNNING) { 588 BString errorMessage(B_TRANSLATE("Launching the network preflet " 589 "failed.\n\nError: ")); 590 errorMessage << strerror(status); 591 BAlert* alert = new BAlert("launch error", errorMessage.String(), 592 B_TRANSLATE("OK")); 593 594 // asynchronous alert in order to not block replicant host application 595 alert->Go(NULL); 596 } 597 } 598 599 600 // #pragma mark - 601 602 603 extern "C" _EXPORT BView * 604 instantiate_deskbar_item(void) 605 { 606 return new NetworkStatusView(BRect(0, 0, 15, 15), 607 B_FOLLOW_LEFT | B_FOLLOW_TOP, true); 608 } 609 610