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