1/*
2 * Copyright 2004-2015 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 *		Alexander von Gluck, kallisti5@unixzen.com
8 *		John Scipione, jscipione@gmail.com
9 */
10
11
12#include "InterfaceView.h"
13
14#include <set>
15
16#include <net/if_media.h>
17
18#include <AutoDeleter.h>
19#include <Button.h>
20#include <Catalog.h>
21#include <ControlLook.h>
22#include <LayoutBuilder.h>
23#include <NetworkAddress.h>
24#include <StringForSize.h>
25#include <StringView.h>
26#include <TextControl.h>
27
28#include "MediaTypes.h"
29#include "WirelessNetworkMenuItem.h"
30
31
32static const uint32 kMsgInterfaceToggle = 'onof';
33static const uint32 kMsgInterfaceRenegotiate = 'redo';
34static const uint32 kMsgJoinNetwork = 'join';
35
36
37#undef B_TRANSLATION_CONTEXT
38#define B_TRANSLATION_CONTEXT "IntefaceView"
39
40
41// #pragma mark - InterfaceView
42
43
44InterfaceView::InterfaceView()
45	:
46	BGroupView(B_VERTICAL),
47	fPulseCount(0)
48{
49	SetFlags(Flags() | B_PULSE_NEEDED);
50
51	// TODO: Small graph of throughput?
52
53	BStringView* statusLabel = new BStringView("status label", B_TRANSLATE("Status:"));
54	statusLabel->SetAlignment(B_ALIGN_RIGHT);
55	fStatusField = new BStringView("status field", "");
56	BStringView* macAddressLabel = new BStringView("mac address label",
57		B_TRANSLATE("MAC address:"));
58	macAddressLabel->SetAlignment(B_ALIGN_RIGHT);
59	fMacAddressField = new BStringView("mac address field", "");
60	BStringView* linkSpeedLabel = new BStringView("link speed label",
61		B_TRANSLATE("Link speed:"));
62	linkSpeedLabel->SetAlignment(B_ALIGN_RIGHT);
63	fLinkSpeedField = new BStringView("link speed field", "");
64
65	// TODO: These metrics may be better in a BScrollView?
66	BStringView* linkTxLabel = new BStringView("tx label",
67		B_TRANSLATE("Sent:"));
68	linkTxLabel->SetAlignment(B_ALIGN_RIGHT);
69	fLinkTxField = new BStringView("tx field", "");
70	BStringView* linkRxLabel = new BStringView("rx label",
71		B_TRANSLATE("Received:"));
72	linkRxLabel->SetAlignment(B_ALIGN_RIGHT);
73	fLinkRxField = new BStringView("rx field", "");
74
75	fNetworkMenuField = new BMenuField(B_TRANSLATE("Network:"), new BMenu(
76		B_TRANSLATE("Choose automatically")));
77	fNetworkMenuField->SetAlignment(B_ALIGN_RIGHT);
78	fNetworkMenuField->Menu()->SetLabelFromMarked(true);
79
80	// Construct the BButtons
81	fToggleButton = new BButton("onoff", B_TRANSLATE("Disable"),
82		new BMessage(kMsgInterfaceToggle));
83
84	fRenegotiateButton = new BButton("heal", B_TRANSLATE("Renegotiate"),
85		new BMessage(kMsgInterfaceRenegotiate));
86	fRenegotiateButton->SetEnabled(false);
87
88	BLayoutBuilder::Group<>(this)
89		.AddGrid()
90			.Add(statusLabel, 0, 0)
91			.Add(fStatusField, 1, 0)
92			.AddMenuField(fNetworkMenuField, 0, 1, B_ALIGN_RIGHT, 1, 2)
93			.Add(macAddressLabel, 0, 2)
94			.Add(fMacAddressField, 1, 2)
95			.Add(linkSpeedLabel, 0, 3)
96			.Add(fLinkSpeedField, 1, 3)
97			.Add(linkTxLabel, 0, 4)
98			.Add(fLinkTxField, 1, 4)
99			.Add(linkRxLabel, 0, 5)
100			.Add(fLinkRxField, 1, 5)
101		.End()
102		.AddGlue()
103		.AddGroup(B_HORIZONTAL)
104			.AddGlue()
105			.Add(fToggleButton)
106			.Add(fRenegotiateButton)
107		.End();
108}
109
110
111InterfaceView::~InterfaceView()
112{
113}
114
115
116void
117InterfaceView::SetTo(const char* name)
118{
119	fInterface.SetTo(name);
120}
121
122
123void
124InterfaceView::AttachedToWindow()
125{
126	_Update();
127		// Populate the fields
128
129	fToggleButton->SetTarget(this);
130	fRenegotiateButton->SetTarget(this);
131}
132
133
134void
135InterfaceView::MessageReceived(BMessage* message)
136{
137	switch (message->what) {
138		case kMsgJoinNetwork:
139		{
140			const char* name;
141			BNetworkAddress address;
142			if (message->FindString("name", &name) == B_OK
143				&& message->FindFlat("address", &address) == B_OK) {
144				BNetworkDevice device(fInterface.Name());
145				status_t status = device.JoinNetwork(address);
146				if (status != B_OK) {
147					// This does not really matter, as it's stored this way,
148					// anyway.
149				}
150				// TODO: store value
151			}
152			break;
153		}
154		case kMsgInterfaceToggle:
155		{
156			// Disable/enable interface
157			uint32 flags = fInterface.Flags();
158			if ((flags & IFF_UP) != 0)
159				flags &= ~IFF_UP;
160			else
161				flags |= IFF_UP;
162
163			if (fInterface.SetFlags(flags) == B_OK)
164				_Update();
165			break;
166		}
167
168		case kMsgInterfaceRenegotiate:
169		{
170			// TODO: renegotiate addresses
171			break;
172		}
173
174		default:
175			BView::MessageReceived(message);
176	}
177}
178
179
180void
181InterfaceView::Pulse()
182{
183	// Update the wireless network menu every 5 seconds
184	_Update((fPulseCount++ % 5) == 0);
185}
186
187
188/*!	Populate fields with current settings.
189*/
190status_t
191InterfaceView::_Update(bool updateWirelessNetworks)
192{
193	BNetworkDevice device(fInterface.Name());
194	bool isWireless = device.IsWireless();
195	bool disabled = (fInterface.Flags() & IFF_UP) == 0;
196
197	if (fInterface.HasLink())
198		fStatusField->SetText(B_TRANSLATE("connected"));
199	else
200		fStatusField->SetText(B_TRANSLATE("disconnected"));
201
202	BNetworkAddress hardwareAddress;
203	if (device.GetHardwareAddress(hardwareAddress) == B_OK)
204		fMacAddressField->SetText(hardwareAddress.ToString());
205	else
206		fMacAddressField->SetText("-");
207
208	int media = fInterface.Media();
209	if ((media & IFM_ACTIVE) != 0)
210		fLinkSpeedField->SetText(media_type_to_string(media));
211	else
212		fLinkSpeedField->SetText("-");
213
214	// Update Link stats
215	ifreq_stats stats;
216	if (fInterface.GetStats(stats) == B_OK) {
217		char buffer[100];
218
219		string_for_size(stats.send.bytes, buffer, sizeof(buffer));
220		fLinkTxField->SetText(buffer);
221
222		string_for_size(stats.receive.bytes, buffer, sizeof(buffer));
223		fLinkRxField->SetText(buffer);
224	}
225
226	// TODO: move the wireless info to a separate tab. We should have a
227	// BListView of available networks, rather than a menu, to make them more
228	// readable and easier to browse and select.
229	if (fNetworkMenuField->IsHidden(fNetworkMenuField) && isWireless)
230		fNetworkMenuField->Show();
231	else if (!fNetworkMenuField->IsHidden(fNetworkMenuField) && !isWireless)
232		fNetworkMenuField->Hide();
233
234	if (isWireless && updateWirelessNetworks) {
235		// Rebuild network menu
236		BMenu* menu = fNetworkMenuField->Menu();
237		int32 count = menu->CountItems();
238
239		// remove non-network items from menu and save them for later
240		BMenuItem* chooseItem = NULL;
241		BSeparatorItem* separatorItem = NULL;
242		if (count > 0 && strcmp(menu->ItemAt(0)->Label(),
243				B_TRANSLATE("Choose automatically")) == 0) {
244			// remove Choose automatically item
245			chooseItem = menu->RemoveItem((int32)0);
246			// remove separator item too
247			separatorItem = (BSeparatorItem*)menu->RemoveItem((int32)0);
248			count -= 2;
249		}
250
251		BMenuItem* noNetworksFoundItem = NULL;
252		if (menu->CountItems() > 0 && strcmp(menu->ItemAt(0)->Label(),
253				B_TRANSLATE("<no wireless networks found>")) == 0) {
254			// remove <no wireless networks found> item
255			noNetworksFoundItem = menu->RemoveItem((int32)0);
256			count--;
257		}
258
259		std::set<BNetworkAddress> associated;
260		BNetworkAddress address;
261		uint32 cookie = 0;
262		while (device.GetNextAssociatedNetwork(cookie, address) == B_OK)
263			associated.insert(address);
264
265		wireless_network* networks = NULL;
266		uint32 networksCount = 0;
267		device.GetNetworks(networks, networksCount);
268
269		if ((fPulseCount % 15) == 0 && networksCount == 0) {
270			// We don't seem to know of any networks, and it's been long
271			// enough since the last scan, so trigger one to try and
272			// find some networks.
273			device.Scan(false, false);
274
275			// We don't want to block for the full length of the scan, but
276			// 50ms is often more than enough to find at least one network,
277			// and the increase in perceived QoS to the user of not seeing
278			// "no wireless networks" if we can avoid it is great enough
279			// to merit such a wait. It's only just over ~4 vertical
280			// retraces, anyway.
281			snooze(50 * 1000);
282
283			device.GetNetworks(networks, networksCount);
284		}
285
286		ArrayDeleter<wireless_network> networksDeleter(networks);
287
288		// go through menu items and remove networks that have dropped out
289		for (int32 index = 0; index < count; index++) {
290			WirelessNetworkMenuItem* networkItem =
291				dynamic_cast<WirelessNetworkMenuItem*>(
292					menu->ItemAt(index));
293			if (networkItem == NULL)
294				break;
295
296			bool networkFound = false;
297			for (uint32 i = 0; i < networksCount; i++) {
298				if (networkItem->Network() == networks[i]) {
299					networkFound = true;
300					break;
301				}
302			}
303
304			if (!networkFound) {
305				menu->RemoveItem(networkItem);
306				count--;
307			}
308		}
309
310		// go through networks and add new ones to menu
311		for (uint32 i = 0; i < networksCount; i++) {
312			const wireless_network& network = networks[i];
313
314			bool networkFound = false;
315			for (int32 index = 0; index < count; index++) {
316				WirelessNetworkMenuItem* networkItem =
317					dynamic_cast<WirelessNetworkMenuItem*>(
318						menu->ItemAt(index));
319				if (networkItem == NULL)
320					break;
321
322				if (networkItem->Network() == network) {
323					// found it
324					networkFound = true;
325					if (associated.find(network.address) != associated.end())
326						networkItem->SetMarked(true);
327					break;
328				}
329			}
330
331			if (!networkFound) {
332				BMessage* message = new BMessage(kMsgJoinNetwork);
333				message->AddString("device", fInterface.Name());
334				message->AddString("name", network.name);
335				message->AddFlat("address", &network.address);
336				BMenuItem* item = new WirelessNetworkMenuItem(network,
337					message);
338				menu->AddItem(item);
339				if (associated.find(network.address) != associated.end())
340					item->SetMarked(true);
341			}
342
343			count++;
344		}
345
346		if (count == 0) {
347			// no networks found
348			if (noNetworksFoundItem != NULL)
349				menu->AddItem(noNetworksFoundItem);
350			else {
351				BMenuItem* item = new BMenuItem(
352					B_TRANSLATE("<no wireless networks found>"), NULL);
353				item->SetEnabled(false);
354				menu->AddItem(item);
355			}
356		} else {
357			// sort items by signal strength
358			menu->SortItems(WirelessNetworkMenuItem::CompareSignalStrength);
359
360			// add Choose automatically item to start
361			if (chooseItem != NULL) {
362				menu->AddItem(chooseItem, 0);
363				menu->AddItem(separatorItem, 1);
364			} else {
365				BMenuItem* item = new BMenuItem(
366					B_TRANSLATE("Choose automatically"), NULL);
367				if (menu->FindMarked() == NULL)
368					item->SetMarked(true);
369				menu->AddItem(item, 0);
370				menu->AddItem(new BSeparatorItem(), 1);
371			}
372		}
373
374		menu->SetTargetForItems(this);
375	}
376
377	//fRenegotiateButton->SetEnabled(!disabled);
378	fToggleButton->SetLabel(disabled
379		? B_TRANSLATE("Enable") : B_TRANSLATE("Disable"));
380
381	return B_OK;
382}
383