xref: /haiku/src/preferences/network/InterfaceView.cpp (revision d891ca1119fc87b4c4fef0baad9ebc49e22b94c9)
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 
32 static const uint32 kMsgInterfaceToggle = 'onof';
33 static const uint32 kMsgInterfaceRenegotiate = 'redo';
34 static 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 
44 InterfaceView::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 
111 InterfaceView::~InterfaceView()
112 {
113 }
114 
115 
116 void
117 InterfaceView::SetTo(const char* name)
118 {
119 	fInterface.SetTo(name);
120 }
121 
122 
123 void
124 InterfaceView::AttachedToWindow()
125 {
126 	_Update();
127 		// Populate the fields
128 
129 	fToggleButton->SetTarget(this);
130 	fRenegotiateButton->SetTarget(this);
131 }
132 
133 
134 void
135 InterfaceView::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 
180 void
181 InterfaceView::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 */
190 status_t
191 InterfaceView::_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