* Copyright 2006-2013, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Dario Casalinuovo
* Axel DΓΆrfler, axeld@pinc-software.de
* Rene Gollent, rene@gollent.com
* Hugo Santos, hugosantos@gmail.com
*/
#include "NetworkStatusView.h"
#include <algorithm>
#include <set>
#include <vector>
#include <arpa/inet.h>
#include <net/if.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <unistd.h>
#include <AboutWindow.h>
#include <Alert.h>
#include <Application.h>
#include <Catalog.h>
#include <Bitmap.h>
#include <Deskbar.h>
#include <Dragger.h>
#include <Drivers.h>
#include <IconUtils.h>
#include <Locale.h>
#include <MenuItem.h>
#include <MessageRunner.h>
#include <NetworkInterface.h>
#include <NetworkRoster.h>
#include <PopUpMenu.h>
#include <Resources.h>
#include <Roster.h>
#include <String.h>
#include <TextView.h>
#include "NetworkStatus.h"
#include "NetworkStatusIcons.h"
#include "RadioView.h"
#include "WirelessNetworkMenuItem.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "NetworkStatusView"
static const char *kStatusDescriptions[] = {
B_TRANSLATE("Unknown"),
B_TRANSLATE("No link"),
B_TRANSLATE("No stateful configuration"),
B_TRANSLATE("Configuring"),
B_TRANSLATE("Ready")
};
extern "C" _EXPORT BView *instantiate_deskbar_item(float maxWidth, float maxHeight);
const uint32 kMsgShowConfiguration = 'shcf';
const uint32 kMsgOpenNetworkPreferences = 'onwp';
const uint32 kMsgJoinNetwork = 'join';
const uint32 kMinIconWidth = 16;
const uint32 kMinIconHeight = 16;
NetworkStatusView::NetworkStatusView(BRect frame, int32 resizingMode,
bool inDeskbar)
: BView(frame, kDeskbarItemName, resizingMode,
B_WILL_DRAW | B_TRANSPARENT_BACKGROUND | B_FRAME_EVENTS),
fInDeskbar(inDeskbar)
{
_Init();
if (!inDeskbar) {
frame.OffsetTo(B_ORIGIN);
frame.top = frame.bottom - 7;
frame.left = frame.right - 7;
BDragger* dragger = new BDragger(frame, this,
B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
AddChild(dragger);
} else
_Update();
}
NetworkStatusView::NetworkStatusView(BMessage* archive)
: BView(archive),
fInDeskbar(false)
{
app_info info;
if (be_app->GetAppInfo(&info) == B_OK
&& !strcasecmp(info.signature, "application/x-vnd.Be-TSKB"))
fInDeskbar = true;
_Init();
}
NetworkStatusView::~NetworkStatusView()
{
}
void
NetworkStatusView::_Init()
{
for (int i = 0; i < kStatusCount; i++) {
fTrayIcons[i] = NULL;
fNotifyIcons[i] = NULL;
}
_UpdateBitmaps();
}
void
NetworkStatusView::_UpdateBitmaps()
{
for (int i = 0; i < kStatusCount; i++) {
delete fTrayIcons[i];
delete fNotifyIcons[i];
fTrayIcons[i] = NULL;
fNotifyIcons[i] = NULL;
}
image_info info;
if (our_image(info) != B_OK)
return;
BFile file(info.name, B_READ_ONLY);
if (file.InitCheck() < B_OK)
return;
BResources resources(&file);
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
if (resources.InitCheck() < B_OK)
return;
#endif
for (int i = 0; i < kStatusCount; i++) {
const void* data = NULL;
size_t size;
data = resources.LoadResource(B_VECTOR_ICON_TYPE,
kNetworkStatusNoDevice + i, &size);
if (data != NULL) {
BBitmap* trayIcon = new BBitmap(Bounds(), B_RGBA32);
if (trayIcon->InitCheck() == B_OK
&& BIconUtils::GetVectorIcon((const uint8 *)data,
size, trayIcon) == B_OK) {
fTrayIcons[i] = trayIcon;
} else
delete trayIcon;
BBitmap* notifyIcon = new BBitmap(BRect(0, 0, 31, 31), B_RGBA32);
if (notifyIcon->InitCheck() == B_OK
&& BIconUtils::GetVectorIcon((const uint8 *)data,
size, notifyIcon) == B_OK) {
fNotifyIcons[i] = notifyIcon;
} else
delete notifyIcon;
}
}
}
void
NetworkStatusView::_Quit()
{
if (fInDeskbar) {
BDeskbar deskbar;
deskbar.RemoveItem(kDeskbarItemName);
} else
be_app->PostMessage(B_QUIT_REQUESTED);
}
NetworkStatusView*
NetworkStatusView::Instantiate(BMessage* archive)
{
if (!validate_instantiation(archive, "NetworkStatusView"))
return NULL;
return new NetworkStatusView(archive);
}
status_t
NetworkStatusView::Archive(BMessage* archive, bool deep) const
{
status_t status = BView::Archive(archive, deep);
if (status == B_OK)
status = archive->AddString("add_on", kSignature);
if (status == B_OK)
status = archive->AddString("class", "NetworkStatusView");
return status;
}
void
NetworkStatusView::AttachedToWindow()
{
BView::AttachedToWindow();
SetViewColor(B_TRANSPARENT_COLOR);
start_watching_network(
B_WATCH_NETWORK_INTERFACE_CHANGES | B_WATCH_NETWORK_LINK_CHANGES, this);
_Update();
}
void
NetworkStatusView::DetachedFromWindow()
{
stop_watching_network(this);
}
void
NetworkStatusView::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_NETWORK_MONITOR:
_Update();
break;
case kMsgShowConfiguration:
_ShowConfiguration(message);
break;
case kMsgOpenNetworkPreferences:
_OpenNetworksPreferences();
break;
case kMsgJoinNetwork:
{
const char* deviceName;
const char* name;
BNetworkAddress address;
if (message->FindString("device", &deviceName) == B_OK
&& message->FindString("name", &name) == B_OK
&& message->FindFlat("address", &address) == B_OK) {
BNetworkDevice device(deviceName);
status_t status = device.JoinNetwork(address);
if (status != B_OK) {
BString text
= B_TRANSLATE("Could not join wireless network:\n");
text << strerror(status);
BAlert* alert = new BAlert(name, text.String(),
B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL,
B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go(NULL);
}
}
break;
}
case B_ABOUT_REQUESTED:
_AboutRequested();
break;
case B_QUIT_REQUESTED:
_Quit();
break;
default:
BView::MessageReceived(message);
break;
}
}
void
NetworkStatusView::FrameResized(float width, float height)
{
_UpdateBitmaps();
Invalidate();
}
void
NetworkStatusView::Draw(BRect updateRect)
{
int32 status = kStatusUnknown;
for (std::map<BString, int32>::const_iterator it
= fInterfaceStatuses.begin(); it != fInterfaceStatuses.end(); ++it) {
if (it->second > status)
status = it->second;
}
if (fTrayIcons[status] == NULL)
return;
SetDrawingMode(B_OP_ALPHA);
DrawBitmap(fTrayIcons[status]);
SetDrawingMode(B_OP_COPY);
}
void
NetworkStatusView::_ShowConfiguration(BMessage* message)
{
const char* name;
if (message->FindString("interface", &name) != B_OK)
return;
BNetworkInterface networkInterface(name);
if (!networkInterface.Exists())
return;
BString text(B_TRANSLATE("%ifaceName information:\n"));
text.ReplaceFirst("%ifaceName", name);
size_t boldLength = text.Length();
int32 numAddrs = networkInterface.CountAddresses();
for (int32 i = 0; i < numAddrs; i++) {
BNetworkInterfaceAddress address;
networkInterface.GetAddressAt(i, address);
switch (address.Address().Family()) {
case AF_INET:
text << "\n" << B_TRANSLATE("IPv4 address:") << " "
<< address.Address().ToString()
<< "\n" << B_TRANSLATE("Broadcast:") << " "
<< address.Broadcast().ToString()
<< "\n" << B_TRANSLATE("Netmask:") << " "
<< address.Mask().ToString()
<< "\n";
break;
case AF_INET6:
text << "\n" << B_TRANSLATE("IPv6 address:") << " "
<< address.Address().ToString()
<< "/" << address.Mask().PrefixLength()
<< "\n";
break;
default:
break;
}
}
BAlert* alert = new BAlert(name, text.String(), B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
BTextView* view = alert->TextView();
BFont font;
view->SetStylable(true);
view->GetFont(&font);
font.SetFace(B_BOLD_FACE);
view->SetFontAndColor(0, boldLength, &font);
alert->Go(NULL);
}
void
NetworkStatusView::MouseDown(BPoint point)
{
BPopUpMenu* menu = new BPopUpMenu(B_EMPTY_STRING, false, false);
menu->SetAsyncAutoDestruct(true);
menu->SetFont(be_plain_font);
BString wifiInterface;
BNetworkDevice device;
if (!fInterfaceStatuses.empty()) {
for (std::map<BString, int32>::const_iterator it
= fInterfaceStatuses.begin(); it != fInterfaceStatuses.end();
++it) {
const BString& name = it->first;
if (wifiInterface.IsEmpty()) {
device.SetTo(name);
if (device.IsWireless())
wifiInterface = name;
}
}
}
if (!wifiInterface.IsEmpty()) {
std::set<BNetworkAddress> associated;
BNetworkAddress address;
uint32 cookie = 0;
while (device.GetNextAssociatedNetwork(cookie, address) == B_OK)
associated.insert(address);
uint32 networksCount = 0;
wireless_network* networks = NULL;
device.GetNetworks(networks, networksCount);
for (uint32 i = 0; i < networksCount; i++) {
const wireless_network& network = networks[i];
BMessage* message = new BMessage(kMsgJoinNetwork);
message->AddString("device", wifiInterface);
message->AddString("name", network.name);
message->AddFlat("address", &network.address);
BMenuItem* item = new WirelessNetworkMenuItem(network, message);
menu->AddItem(item);
if (associated.find(network.address) != associated.end())
item->SetMarked(true);
}
delete[] networks;
if (networksCount == 0) {
BMenuItem* item = new BMenuItem(
B_TRANSLATE("<no wireless networks found>"), NULL);
item->SetEnabled(false);
menu->AddItem(item);
} else
menu->SortItems(WirelessNetworkMenuItem::CompareSignalStrength);
menu->AddSeparatorItem();
}
menu->AddItem(new BMenuItem(B_TRANSLATE(
"Open network preferences" B_UTF8_ELLIPSIS),
new BMessage(kMsgOpenNetworkPreferences)));
if (fInDeskbar) {
menu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
new BMessage(B_QUIT_REQUESTED)));
}
if (!fInterfaceStatuses.empty()) {
int32 wiredCount = 0;
for (std::map<BString, int32>::const_iterator it
= fInterfaceStatuses.begin(); it != fInterfaceStatuses.end();
++it) {
const BString& name = it->first;
BString label = name;
label += ": ";
label += kStatusDescriptions[
_DetermineInterfaceStatus(name.String())];
BMessage* info = new BMessage(kMsgShowConfiguration);
info->AddString("interface", name.String());
menu->AddItem(new BMenuItem(label.String(), info), wiredCount);
wiredCount++;
}
if (wiredCount > 0)
menu->AddItem(new BSeparatorItem(), wiredCount);
}
menu->SetTargetForItems(this);
ConvertToScreen(&point);
menu->Go(point, true, true, true);
}
void
NetworkStatusView::_AboutRequested()
{
BAboutWindow* window = new BAboutWindow(
B_TRANSLATE_SYSTEM_NAME("NetworkStatus"), kSignature);
const char* authors[] = {
"Axel DΓΆrfler",
"Hugo Santos",
NULL
};
window->AddCopyright(2007, "Haiku, Inc.");
window->AddAuthors(authors);
window->Show();
}
int32
NetworkStatusView::_DetermineInterfaceStatus(
const BNetworkInterface& interface)
{
uint32 flags = interface.Flags();
if ((flags & IFF_LINK) == 0)
return kStatusNoLink;
if ((flags & (IFF_UP | IFF_LINK | IFF_CONFIGURING)) == IFF_LINK)
return kStatusLinkNoConfig;
if ((flags & IFF_CONFIGURING) == IFF_CONFIGURING)
return kStatusConnecting;
if ((flags & (IFF_UP | IFF_LINK)) == (IFF_UP | IFF_LINK))
return kStatusReady;
return kStatusUnknown;
}
void
NetworkStatusView::_Update(bool force)
{
BNetworkRoster& roster = BNetworkRoster::Default();
BNetworkInterface interface;
uint32 cookie = 0;
std::set<BString> currentInterfaces;
while (roster.GetNextInterface(&cookie, interface) == B_OK) {
if ((interface.Flags() & IFF_LOOPBACK) == 0) {
currentInterfaces.insert((BString)interface.Name());
int32 oldStatus = kStatusUnknown;
if (fInterfaceStatuses.find(interface.Name())
!= fInterfaceStatuses.end()) {
oldStatus = fInterfaceStatuses[interface.Name()];
}
int32 status = _DetermineInterfaceStatus(interface);
if (oldStatus != status) {
BNotification notification(B_INFORMATION_NOTIFICATION);
notification.SetGroup(B_TRANSLATE("Network Status"));
notification.SetTitle(interface.Name());
notification.SetMessageID(interface.Name());
notification.SetIcon(fNotifyIcons[status]);
if (status == kStatusConnecting
|| (status == kStatusReady
&& oldStatus == kStatusConnecting)
|| (status == kStatusNoLink
&& oldStatus == kStatusReady)
|| (status == kStatusNoLink
&& oldStatus == kStatusConnecting)) {
notification.SetContent(kStatusDescriptions[status]);
notification.Send();
}
Invalidate();
}
fInterfaceStatuses[interface.Name()] = status;
}
}
std::map<BString, int32>::iterator it = fInterfaceStatuses.begin();
while (it != fInterfaceStatuses.end()) {
std::map<BString, int32>::iterator backupIt = it;
if (currentInterfaces.find(it->first) == currentInterfaces.end())
fInterfaceStatuses.erase(it);
it = ++backupIt;
}
}
void
NetworkStatusView::_OpenNetworksPreferences()
{
status_t status = be_roster->Launch("application/x-vnd.Haiku-Network");
if (status != B_OK && status != B_ALREADY_RUNNING) {
BString errorMessage(B_TRANSLATE("Launching the network preflet "
"failed.\n\nError: "));
errorMessage << strerror(status);
BAlert* alert = new BAlert("launch error", errorMessage.String(),
B_TRANSLATE("OK"));
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go(NULL);
}
}
extern "C" _EXPORT BView *
instantiate_deskbar_item(float maxWidth, float maxHeight)
{
return new NetworkStatusView(BRect(0, 0, maxHeight - 1, maxHeight - 1),
B_FOLLOW_LEFT | B_FOLLOW_TOP, true);
}