1 /*
2 * Copyright 2004-2019 Haiku Inc., All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 * Adrien Destugues, <pulkomandy@pulkomandy.tk>
7 * Axel Dörfler, <axeld@pinc-software.de>
8 * Alexander von Gluck, <kallisti5@unixzen.com>
9 */
10
11
12 #include "NetworkWindow.h"
13
14 #include <net/if.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18
19 #include <Alert.h>
20 #include <Application.h>
21 #include <Button.h>
22 #include <Catalog.h>
23 #include <CheckBox.h>
24 #include <ControlLook.h>
25 #include <Deskbar.h>
26 #include <Directory.h>
27 #include <LayoutBuilder.h>
28 #include <NetworkDevice.h>
29 #include <NetworkInterface.h>
30 #include <NetworkNotifications.h>
31 #include <NetworkRoster.h>
32 #include <OutlineListView.h>
33 #include <Path.h>
34 #include <PathFinder.h>
35 #include <PathMonitor.h>
36 #include <Roster.h>
37 #include <ScrollView.h>
38 #include <StringItem.h>
39 #include <SymLink.h>
40
41 #define ENABLE_PROFILES 0
42 #if ENABLE_PROFILES
43 # include <PopUpMenu.h>
44 #endif
45
46 #include "InterfaceListItem.h"
47 #include "InterfaceView.h"
48 #include "ServiceListItem.h"
49
50
51 const char* kNetworkStatusSignature = "application/x-vnd.Haiku-NetworkStatus";
52
53 static const uint32 kMsgProfileSelected = 'prof';
54 static const uint32 kMsgProfileManage = 'mngp';
55 static const uint32 kMsgProfileNew = 'newp';
56 static const uint32 kMsgRevert = 'rvrt';
57 static const uint32 kMsgToggleReplicant = 'trep';
58 static const uint32 kMsgItemSelected = 'ItSl';
59
60 BMessenger gNetworkWindow;
61
62
63 #undef B_TRANSLATION_CONTEXT
64 #define B_TRANSLATION_CONTEXT "NetworkWindow"
65
66
67 class TitleItem : public BStringItem {
68 public:
TitleItem(const char * title)69 TitleItem(const char* title)
70 :
71 BStringItem(title)
72 {
73 }
74
DrawItem(BView * owner,BRect bounds,bool complete)75 void DrawItem(BView* owner, BRect bounds, bool complete)
76 {
77 owner->SetFont(be_bold_font);
78 BStringItem::DrawItem(owner, bounds, complete);
79 owner->SetFont(be_plain_font);
80 }
81
Update(BView * owner,const BFont * font)82 void Update(BView* owner, const BFont* font)
83 {
84 BStringItem::Update(owner, be_bold_font);
85 }
86 };
87
88
89 // #pragma mark -
90
91
NetworkWindow()92 NetworkWindow::NetworkWindow()
93 :
94 BWindow(BRect(100, 100, 750, 400), B_TRANSLATE_SYSTEM_NAME("Network"),
95 B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS | B_NOT_ZOOMABLE
96 | B_AUTO_UPDATE_SIZE_LIMITS),
97 fServicesItem(NULL),
98 fDialUpItem(NULL),
99 fVPNItem(NULL),
100 fOtherItem(NULL)
101 {
102 // Profiles section
103 #if ENABLE_PROFILES
104 BPopUpMenu* profilesPopup = new BPopUpMenu("<none>");
105 _BuildProfilesMenu(profilesPopup, kMsgProfileSelected);
106
107 BMenuField* profilesMenuField = new BMenuField("profiles_menu",
108 B_TRANSLATE("Profile:"), profilesPopup);
109
110 profilesMenuField->SetFont(be_bold_font);
111 profilesMenuField->SetEnabled(false);
112 #endif
113
114 // Settings section
115
116 fRevertButton = new BButton("revert", B_TRANSLATE("Revert"),
117 new BMessage(kMsgRevert));
118
119 BMessage* message = new BMessage(kMsgToggleReplicant);
120 BCheckBox* showReplicantCheckBox = new BCheckBox("showReplicantCheckBox",
121 B_TRANSLATE("Show network status in Deskbar"), message);
122 showReplicantCheckBox->SetExplicitMaxSize(
123 BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));
124 showReplicantCheckBox->SetValue(_IsReplicantInstalled());
125
126 fListView = new BOutlineListView("list", B_SINGLE_SELECTION_LIST,
127 B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_FRAME_EVENTS | B_NAVIGABLE);
128 fListView->SetSelectionMessage(new BMessage(kMsgItemSelected));
129
130 BScrollView* scrollView = new BScrollView("ScrollView", fListView,
131 0, false, true);
132 scrollView->SetExplicitMaxSize(BSize(B_SIZE_UNSET, B_SIZE_UNLIMITED));
133
134 fAddOnShellView = new BView("add-on shell", 0,
135 new BGroupLayout(B_VERTICAL));
136 fAddOnShellView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
137 fAddOnShellView->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
138
139 fInterfaceView = new InterfaceView();
140
141 // Build the layout
142 BLayoutBuilder::Group<>(this, B_VERTICAL)
143 .SetInsets(B_USE_WINDOW_SPACING)
144
145 #if ENABLE_PROFILES
146 .AddGroup(B_HORIZONTAL, B_USE_SMALL_SPACING)
147 .Add(profilesMenuField)
148 .AddGlue()
149 .End()
150 #endif
151 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
152 .Add(scrollView)
153 .Add(fAddOnShellView)
154 .End()
155
156 .Add(showReplicantCheckBox)
157 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING)
158 .Add(fRevertButton)
159 .AddGlue()
160 .End();
161
162 gNetworkWindow = this;
163
164 _ScanInterfaces();
165 _ScanAddOns();
166 _UpdateRevertButton();
167
168 fListView->Select(0);
169 _SelectItem(fListView->ItemAt(0));
170 // Call this manually, so that CenterOnScreen() below already
171 // knows the final window size.
172
173 // Set size of the list view from its contents
174 float width;
175 float height;
176 fListView->GetPreferredSize(&width, &height);
177 width += 2 * be_control_look->DefaultItemSpacing();
178 fListView->SetExplicitSize(BSize(width, B_SIZE_UNSET));
179 fListView->SetExplicitMinSize(BSize(width, std::min(height, 400.f)));
180
181 CenterOnScreen();
182
183 fSettings.StartMonitoring(this);
184 start_watching_network(B_WATCH_NETWORK_INTERFACE_CHANGES
185 | B_WATCH_NETWORK_LINK_CHANGES | B_WATCH_NETWORK_WLAN_CHANGES, this);
186 }
187
188
~NetworkWindow()189 NetworkWindow::~NetworkWindow()
190 {
191 stop_watching_network(this);
192 fSettings.StopMonitoring(this);
193 }
194
195
196 bool
QuitRequested()197 NetworkWindow::QuitRequested()
198 {
199 be_app->PostMessage(B_QUIT_REQUESTED);
200 return true;
201 }
202
203
204 void
MessageReceived(BMessage * message)205 NetworkWindow::MessageReceived(BMessage* message)
206 {
207 switch (message->what) {
208 case kMsgProfileNew:
209 break;
210
211 case kMsgProfileSelected:
212 {
213 const char* path;
214 if (message->FindString("path", &path) != B_OK)
215 break;
216
217 // TODO!
218 break;
219 }
220
221 case kMsgItemSelected:
222 {
223 BListItem* listItem = fListView->FullListItemAt(
224 fListView->FullListCurrentSelection());
225 if (listItem == NULL)
226 break;
227
228 _SelectItem(listItem);
229 break;
230 }
231
232 case kMsgRevert:
233 {
234 SettingsMap::const_iterator iterator = fSettingsMap.begin();
235 for (; iterator != fSettingsMap.end(); iterator++)
236 iterator->second->Revert();
237 break;
238 }
239
240 case kMsgToggleReplicant:
241 {
242 _ShowReplicant(
243 message->GetInt32("be:value", B_CONTROL_OFF) == B_CONTROL_ON);
244 break;
245 }
246
247 case B_PATH_MONITOR:
248 {
249 fSettings.Update(message);
250 break;
251 }
252
253 case B_NETWORK_MONITOR:
254 _BroadcastConfigurationUpdate(*message);
255 break;
256
257 case BNetworkSettings::kMsgInterfaceSettingsUpdated:
258 case BNetworkSettings::kMsgNetworkSettingsUpdated:
259 case BNetworkSettings::kMsgServiceSettingsUpdated:
260 _BroadcastSettingsUpdate(message->what);
261 break;
262
263 case kMsgSettingsItemUpdated:
264 // TODO: update list item
265 _UpdateRevertButton();
266 break;
267
268 default:
269 inherited::MessageReceived(message);
270 }
271 }
272
273
274 void
_BuildProfilesMenu(BMenu * menu,int32 what)275 NetworkWindow::_BuildProfilesMenu(BMenu* menu, int32 what)
276 {
277 char currentProfile[256] = { 0 };
278
279 menu->SetRadioMode(true);
280
281 BDirectory dir("/boot/system/settings/network/profiles");
282 if (dir.InitCheck() == B_OK) {
283 BEntry entry;
284
285 dir.Rewind();
286 while (dir.GetNextEntry(&entry) >= 0) {
287 BPath name;
288 entry.GetPath(&name);
289
290 if (entry.IsSymLink() &&
291 strcmp("current", name.Leaf()) == 0) {
292 BSymLink symlink(&entry);
293
294 if (symlink.IsAbsolute())
295 // oh oh, sorry, wrong symlink...
296 continue;
297
298 symlink.ReadLink(currentProfile, sizeof(currentProfile));
299 continue;
300 };
301
302 if (!entry.IsDirectory())
303 continue;
304
305 BMessage* message = new BMessage(what);
306 message->AddString("path", name.Path());
307
308 BMenuItem* item = new BMenuItem(name.Leaf(), message);
309 menu->AddItem(item);
310 }
311 }
312
313 menu->AddSeparatorItem();
314 menu->AddItem(new BMenuItem(B_TRANSLATE("New" B_UTF8_ELLIPSIS),
315 new BMessage(kMsgProfileNew)));
316 menu->AddItem(new BMenuItem(B_TRANSLATE("Manage" B_UTF8_ELLIPSIS),
317 new BMessage(kMsgProfileManage)));
318
319 if (currentProfile[0] != '\0') {
320 BMenuItem* item = menu->FindItem(currentProfile);
321 if (item != NULL) {
322 // TODO: translate
323 BString label(item->Label());
324 label << " (current)";
325 item->SetLabel(label.String());
326 item->SetMarked(true);
327 }
328 }
329 }
330
331
332 void
_ScanInterfaces()333 NetworkWindow::_ScanInterfaces()
334 {
335 // Try existing devices first
336 BNetworkRoster& roster = BNetworkRoster::Default();
337 BNetworkInterface interface;
338 uint32 cookie = 0;
339
340 while (roster.GetNextInterface(&cookie, interface) == B_OK) {
341 if ((interface.Flags() & IFF_LOOPBACK) != 0)
342 continue;
343
344 BNetworkDevice device(interface.Name());
345 BNetworkInterfaceType type = B_NETWORK_INTERFACE_TYPE_OTHER;
346
347 if (device.IsWireless())
348 type = B_NETWORK_INTERFACE_TYPE_WIFI;
349 else if (device.IsEthernet())
350 type = B_NETWORK_INTERFACE_TYPE_ETHERNET;
351
352 InterfaceListItem* item = new InterfaceListItem(interface.Name(), type);
353 item->SetExpanded(true);
354
355 fInterfaceItemMap.insert(std::pair<BString, InterfaceListItem*>(
356 BString(interface.Name()), item));
357 fListView->AddItem(item);
358 }
359
360 // TODO: Then consider those from the settings (for example, for USB)
361 }
362
363
364 void
_ScanAddOns()365 NetworkWindow::_ScanAddOns()
366 {
367 BStringList paths;
368 BPathFinder::FindPaths(B_FIND_PATH_ADD_ONS_DIRECTORY, "Network Settings",
369 paths);
370
371 // Collect add-on paths by name, so that each name will only be
372 // loaded once.
373 typedef std::map<BString, BPath> PathMap;
374 PathMap addOnMap;
375
376 for (int32 i = 0; i < paths.CountStrings(); i++) {
377 BDirectory directory(paths.StringAt(i));
378 BEntry entry;
379 while (directory.GetNextEntry(&entry) == B_OK) {
380 BPath path;
381 if (entry.GetPath(&path) != B_OK)
382 continue;
383
384 if (addOnMap.find(path.Leaf()) == addOnMap.end())
385 addOnMap.insert(std::pair<BString, BPath>(path.Leaf(), path));
386 }
387 }
388
389 for (PathMap::const_iterator addOnIterator = addOnMap.begin();
390 addOnIterator != addOnMap.end(); addOnIterator++) {
391 const BPath& path = addOnIterator->second;
392
393 image_id image = load_add_on(path.Path());
394 if (image < 0) {
395 printf("Failed to load %s addon: %s.\n", path.Path(),
396 strerror(image));
397 continue;
398 }
399
400 BNetworkSettingsAddOn* (*instantiateAddOn)(image_id image,
401 BNetworkSettings& settings);
402
403 status_t status = get_image_symbol(image,
404 "instantiate_network_settings_add_on",
405 B_SYMBOL_TYPE_TEXT, (void**)&instantiateAddOn);
406 if (status != B_OK) {
407 // No "addon instantiate function" symbol found in this addon
408 printf("No symbol \"instantiate_network_settings_add_on\" found "
409 "in %s addon: not a network setup addon!\n", path.Path());
410 unload_add_on(image);
411 continue;
412 }
413
414 BNetworkSettingsAddOn* addOn = instantiateAddOn(image, fSettings);
415 if (addOn == NULL) {
416 unload_add_on(image);
417 continue;
418 }
419
420 fAddOns.AddItem(addOn);
421
422 // Per interface items
423 ItemMap::const_iterator iterator = fInterfaceItemMap.begin();
424 for (; iterator != fInterfaceItemMap.end(); iterator++) {
425 const BString& interface = iterator->first;
426 BListItem* interfaceItem = iterator->second;
427
428 uint32 cookie = 0;
429 while (true) {
430 BNetworkSettingsItem* item = addOn->CreateNextInterfaceItem(
431 cookie, interface.String());
432 if (item == NULL)
433 break;
434
435 fSettingsMap[item->ListItem()] = item;
436 fListView->AddUnder(item->ListItem(), interfaceItem);
437 }
438 fListView->SortItemsUnder(interfaceItem, true,
439 NetworkWindow::_CompareListItems);
440 }
441
442 // Generic items
443 uint32 cookie = 0;
444 while (true) {
445 BNetworkSettingsItem* item = addOn->CreateNextItem(cookie);
446 if (item == NULL)
447 break;
448
449 fSettingsMap[item->ListItem()] = item;
450 fListView->AddUnder(item->ListItem(),
451 _ListItemFor(item->Type()));
452 }
453
454 _SortItemsUnder(fServicesItem);
455 _SortItemsUnder(fOtherItem);
456 }
457
458 fListView->SortItemsUnder(NULL, true,
459 NetworkWindow::_CompareTopLevelListItems);
460 }
461
462
463 BNetworkSettingsItem*
_SettingsItemFor(BListItem * item)464 NetworkWindow::_SettingsItemFor(BListItem* item)
465 {
466 SettingsMap::const_iterator found = fSettingsMap.find(item);
467 if (found != fSettingsMap.end())
468 return found->second;
469
470 return NULL;
471 }
472
473
474 void
_SortItemsUnder(BListItem * item)475 NetworkWindow::_SortItemsUnder(BListItem* item)
476 {
477 if (item != NULL)
478 fListView->SortItemsUnder(item, true, NetworkWindow::_CompareListItems);
479 }
480
481
482 BListItem*
_ListItemFor(BNetworkSettingsType type)483 NetworkWindow::_ListItemFor(BNetworkSettingsType type)
484 {
485 switch (type) {
486 case B_NETWORK_SETTINGS_TYPE_SERVICE:
487 if (fServicesItem == NULL)
488 fServicesItem = _CreateItem(B_TRANSLATE("Services"));
489 return fServicesItem;
490
491 case B_NETWORK_SETTINGS_TYPE_OTHER:
492 if (fOtherItem == NULL)
493 fOtherItem = _CreateItem(B_TRANSLATE("Other"));
494 return fOtherItem;
495
496 default:
497 return NULL;
498 }
499 }
500
501
502 BListItem*
_CreateItem(const char * label)503 NetworkWindow::_CreateItem(const char* label)
504 {
505 BListItem* item = new TitleItem(label);
506 item->SetExpanded(true);
507 fListView->AddItem(item);
508 return item;
509 }
510
511
512 void
_SelectItem(BListItem * listItem)513 NetworkWindow::_SelectItem(BListItem* listItem)
514 {
515 while (fAddOnShellView->CountChildren() > 0)
516 fAddOnShellView->ChildAt(0)->RemoveSelf();
517
518 BView* nextView = NULL;
519
520 BNetworkSettingsItem* item = _SettingsItemFor(listItem);
521 if (item != NULL) {
522 nextView = item->View();
523 } else {
524 InterfaceListItem* item = dynamic_cast<InterfaceListItem*>(
525 listItem);
526 if (item != NULL) {
527 fInterfaceView->SetTo(item->Name());
528 nextView = fInterfaceView;
529 }
530 }
531
532 if (nextView != NULL)
533 fAddOnShellView->AddChild(nextView);
534 }
535
536
537 void
_BroadcastSettingsUpdate(uint32 type)538 NetworkWindow::_BroadcastSettingsUpdate(uint32 type)
539 {
540 for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
541 BNetworkSettingsListener* listener
542 = dynamic_cast<BNetworkSettingsListener*>(
543 fListView->FullListItemAt(index));
544 if (listener != NULL)
545 listener->SettingsUpdated(type);
546 }
547
548 SettingsMap::const_iterator iterator = fSettingsMap.begin();
549 for (; iterator != fSettingsMap.end(); iterator++)
550 iterator->second->SettingsUpdated(type);
551
552 _UpdateRevertButton();
553 }
554
555
556 void
_BroadcastConfigurationUpdate(const BMessage & message)557 NetworkWindow::_BroadcastConfigurationUpdate(const BMessage& message)
558 {
559 for (int32 index = 0; index < fListView->FullListCountItems(); index++) {
560 BNetworkConfigurationListener* listener
561 = dynamic_cast<BNetworkConfigurationListener*>(
562 fListView->FullListItemAt(index));
563 if (listener != NULL)
564 listener->ConfigurationUpdated(message);
565 }
566
567 SettingsMap::const_iterator iterator = fSettingsMap.begin();
568 for (; iterator != fSettingsMap.end(); iterator++)
569 iterator->second->ConfigurationUpdated(message);
570
571 // TODO: improve invalidated region to the one that matters
572 fListView->Invalidate();
573 _UpdateRevertButton();
574 }
575
576
577 void
_UpdateRevertButton()578 NetworkWindow::_UpdateRevertButton()
579 {
580 bool enabled = false;
581 SettingsMap::const_iterator iterator = fSettingsMap.begin();
582 for (; iterator != fSettingsMap.end(); iterator++) {
583 if (iterator->second->IsRevertable()) {
584 enabled = true;
585 break;
586 }
587 }
588
589 fRevertButton->SetEnabled(enabled);
590 }
591
592
593 void
_ShowReplicant(bool show)594 NetworkWindow::_ShowReplicant(bool show)
595 {
596 if (show) {
597 const char* argv[] = {"--deskbar", NULL};
598
599 status_t status = be_roster->Launch(kNetworkStatusSignature, 1, argv);
600 if (status != B_OK) {
601 BString errorMessage;
602 errorMessage.SetToFormat(
603 B_TRANSLATE("Installing NetworkStatus in Deskbar failed: %s"),
604 strerror(status));
605 BAlert* alert = new BAlert(B_TRANSLATE("launch error"),
606 errorMessage, B_TRANSLATE("OK"));
607 alert->Go(NULL);
608 }
609 } else {
610 BDeskbar deskbar;
611 deskbar.RemoveItem("NetworkStatus");
612 }
613 }
614
615
616 bool
_IsReplicantInstalled()617 NetworkWindow::_IsReplicantInstalled()
618 {
619 BDeskbar deskbar;
620 return deskbar.HasItem("NetworkStatus");
621 }
622
623
624 /*static*/ const char*
_ItemName(const BListItem * item)625 NetworkWindow::_ItemName(const BListItem* item)
626 {
627 if (const BNetworkInterfaceListItem* listItem = dynamic_cast<
628 const BNetworkInterfaceListItem*>(item))
629 return listItem->Label();
630
631 if (const ServiceListItem* listItem = dynamic_cast<
632 const ServiceListItem*>(item))
633 return listItem->Label();
634
635 if (const BStringItem* stringItem = dynamic_cast<const BStringItem*>(item))
636 return stringItem->Text();
637
638 return NULL;
639 }
640
641
642 /*static*/ int
_CompareTopLevelListItems(const BListItem * a,const BListItem * b)643 NetworkWindow::_CompareTopLevelListItems(const BListItem* a, const BListItem* b)
644 {
645 if (a == b)
646 return 0;
647
648 if (const InterfaceListItem* itemA
649 = dynamic_cast<const InterfaceListItem*>(a)) {
650 if (const InterfaceListItem* itemB
651 = dynamic_cast<const InterfaceListItem*>(b)) {
652 return strcasecmp(itemA->Name(), itemB->Name());
653 }
654 return -1;
655 } else if (dynamic_cast<const InterfaceListItem*>(b) != NULL)
656 return 1;
657 /*
658 if (a == fDialUpItem)
659 return -1;
660 if (b == fDialUpItem)
661 return 1;
662
663 if (a == fServicesItem)
664 return -1;
665 if (b == fServicesItem)
666 return 1;
667 */
668 return _CompareListItems(a, b);
669 }
670
671
672 /*static*/ int
_CompareListItems(const BListItem * a,const BListItem * b)673 NetworkWindow::_CompareListItems(const BListItem* a, const BListItem* b)
674 {
675 if (a == b)
676 return 0;
677
678 const char* nameA = _ItemName(a);
679 const char* nameB = _ItemName(b);
680
681 if (nameA != NULL && nameB != NULL)
682 return strcasecmp(nameA, nameB);
683 if (nameA != NULL)
684 return 1;
685 if (nameB != NULL)
686 return -1;
687
688 return (addr_t)a > (addr_t)b ? 1 : -1;
689 }
690