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