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