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