MainPage.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. #include "pch.h"
  2. #include "MainPage.h"
  3. #include "MainPage.g.cpp"
  4. #include <filesystem>
  5. #include <winrt/Windows.ApplicationModel.Core.h>
  6. #include <winrt/Windows.UI.ViewManagement.h>
  7. #include <winrt/Windows.UI.Xaml.Media.h>
  8. #include "Model\Netif.h"
  9. #include "UI.h"
  10. namespace winrt::Maple_App::implementation
  11. {
  12. using namespace std::chrono_literals;
  13. using namespace winrt::Windows::UI::ViewManagement;
  14. using namespace winrt::Windows::UI::Xaml;
  15. using namespace winrt::Windows::UI::Xaml::Media;
  16. std::string getNormalizedExtentionFromPath(const winrt::hstring& path) {
  17. auto ext = std::filesystem::path(std::wstring_view(path)).extension().string();
  18. std::transform(ext.begin(), ext.end(), ext.begin(), [](const auto ch) {
  19. return static_cast<char>(std::tolower(ch));
  20. });
  21. return ext;
  22. }
  23. MainPage::MainPage()
  24. {
  25. InitializeComponent();
  26. const auto coreTitleBar{ CoreApplication::GetCurrentView().TitleBar() };
  27. const auto window{ Window::Current() };
  28. coreTitleBar.ExtendViewIntoTitleBar(true);
  29. window.SetTitleBar(CustomTitleBar());
  30. coreTitleBar.LayoutMetricsChanged({ this,&MainPage::CoreTitleBar_LayoutMetricsChanged });
  31. window.Activated({ this, &MainPage::CoreWindow_Activated });
  32. }
  33. void MainPage::CoreTitleBar_LayoutMetricsChanged(CoreApplicationViewTitleBar const& coreTitleBar, IInspectable const&)
  34. {
  35. LeftPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayLeftInset(), .GridUnitType = GridUnitType::Pixel });
  36. RightPaddingColumn().Width(GridLength{ .Value = coreTitleBar.SystemOverlayRightInset(), .GridUnitType = GridUnitType::Pixel });
  37. }
  38. void MainPage::CoreWindow_Activated(IInspectable const&, WindowActivatedEventArgs const& args)
  39. {
  40. UISettings settings{};
  41. if (args.WindowActivationState() == CoreWindowActivationState::Deactivated)
  42. {
  43. AppTitleTextBlock().Foreground(
  44. SolidColorBrush{ settings.UIElementColor(UIElementType::GrayText) });
  45. }
  46. else
  47. {
  48. AppTitleTextBlock().Foreground(
  49. SolidColorBrush{ settings.GetColorValue(UIColorType::Foreground) });
  50. }
  51. }
  52. DependencyProperty MainPage::ConfigItemsProperty()
  53. {
  54. return m_configItemsProperty;
  55. }
  56. IObservableVector<Maple_App::ConfigViewModel> MainPage::ConfigItems()
  57. {
  58. return GetValue(m_configItemsProperty).as<IObservableVector<Maple_App::ConfigViewModel>>();
  59. }
  60. void MainPage::Page_Loaded(IInspectable const&, RoutedEventArgs const&)
  61. {
  62. const auto _ = LoadConfigs();
  63. NavigationManager = SystemNavigationManager::GetForCurrentView();
  64. NavigationManager.AppViewBackButtonVisibility(MainSplitView().IsPaneOpen()
  65. ? AppViewBackButtonVisibility::Collapsed
  66. : AppViewBackButtonVisibility::Visible);
  67. const auto weakThis = get_weak();
  68. NavigationManager.BackRequested([weakThis](const auto&, const auto&) {
  69. if (const auto self{ weakThis.get() }) {
  70. self->MainSplitView().IsPaneOpen(true);
  71. const auto currentVisibility = NavigationManager.AppViewBackButtonVisibility();
  72. if (currentVisibility == AppViewBackButtonVisibility::Visible) {
  73. NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Disabled);
  74. }
  75. }
  76. });
  77. StartConnectionCheck();
  78. }
  79. IAsyncAction MainPage::NotifyUser(const hstring& message) {
  80. ContentDialog dialog;
  81. dialog.Content(box_value(message));
  82. dialog.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
  83. co_await dialog.ShowAsync();
  84. }
  85. IAsyncOperation<IStorageFolder> MainPage::InitializeConfigFolder()
  86. {
  87. const auto& appData = ApplicationData::Current();
  88. const auto& localFolder = appData.LocalFolder();
  89. auto configItem = co_await localFolder.TryGetItemAsync(L"config");
  90. if (configItem == nullptr || configItem.IsOfType(StorageItemTypes::File)) {
  91. configItem = co_await localFolder.CreateFolderAsync(L"config", CreationCollisionOption::ReplaceExisting);
  92. }
  93. co_return configItem.as<IStorageFolder>();
  94. }
  95. IAsyncOperation<StorageFile> MainPage::CopyDefaultConfig(const IStorageFolder& configFolder, std::wstring_view path, const hstring& desiredName)
  96. {
  97. const auto& defaultConfigSrc = co_await StorageFile::GetFileFromApplicationUriAsync(Uri{ path });
  98. co_return co_await defaultConfigSrc.CopyAsync(configFolder, desiredName, NameCollisionOption::GenerateUniqueName);
  99. }
  100. IAsyncAction MainPage::LoadConfigs()
  101. {
  102. const auto lifetime = get_strong();
  103. const auto& appData = ApplicationData::Current();
  104. m_configFolder = co_await InitializeConfigFolder();
  105. auto configFiles = co_await m_configFolder.GetFilesAsync();
  106. if (configFiles.Size() == 0) {
  107. const auto& defaultConfigDst = co_await CopyDefaultConfig(m_configFolder, DEFAULT_CONF_FILE_PATH, L"default.conf");
  108. appData.LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(defaultConfigDst.Path()));
  109. configFiles = co_await m_configFolder.GetFilesAsync();
  110. }
  111. std::vector<Maple_App::ConfigViewModel> configModels;
  112. configModels.reserve(static_cast<size_t>(configFiles.Size()));
  113. const auto& defaultConfigPath = appData.LocalSettings().Values().TryLookup(CONFIG_PATH_SETTING_KEY).try_as<hstring>();
  114. for (const auto& file : configFiles) {
  115. const auto isDefault = file.Path() == defaultConfigPath;
  116. const auto& instance = configModels.emplace_back(co_await ConfigViewModel::FromFile(file, isDefault));
  117. if (isDefault) {
  118. m_defaultConfig = instance;
  119. }
  120. }
  121. SetValue(m_configItemsProperty, single_threaded_observable_vector<Maple_App::ConfigViewModel>(std::move(configModels)));
  122. ConfigListView().SelectedItem(m_defaultConfig);
  123. }
  124. void MainPage::ConfigSetAsDefaultMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  125. {
  126. try {
  127. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  128. if (item == nullptr) {
  129. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  130. }
  131. SetAsDefault(item);
  132. }
  133. catch (...)
  134. {
  135. UI::NotifyException(L"Setting default");
  136. }
  137. }
  138. void MainPage::ConfigItem_DoubleTapped(IInspectable const& sender, DoubleTappedRoutedEventArgs const&)
  139. {
  140. const auto& item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  141. SetAsDefault(item);
  142. }
  143. void MainPage::SetAsDefault(const Maple_App::ConfigViewModel& item)
  144. {
  145. try {
  146. const auto name = getNormalizedExtentionFromPath(item.Name());
  147. if (name != ".conf" && name != ".json") {
  148. NotifyUser(L"A valid configuration file must end with .conf or .json.");
  149. return;
  150. }
  151. ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
  152. m_defaultConfig.IsDefault(false);
  153. item.IsDefault(true);
  154. m_defaultConfig = item;
  155. }
  156. catch (...)
  157. {
  158. UI::NotifyException(L"Setting default");
  159. }
  160. }
  161. void MainPage::ConfigRenameMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  162. {
  163. try {
  164. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  165. if (item == nullptr) {
  166. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  167. }
  168. RequestRenameItem(item);
  169. }
  170. catch (...)
  171. {
  172. UI::NotifyException(L"Requesting rename");
  173. }
  174. }
  175. void MainPage::RequestRenameItem(const Maple_App::ConfigViewModel& item)
  176. {
  177. const auto& name = item.Name();
  178. const auto& renameDialog = RenameDialog();
  179. const auto& renameDialogText = RenameDialogText();
  180. renameDialogText.Text(name);
  181. auto it = std::find(name.rbegin(), name.rend(), '.');
  182. if (it != name.rend()) {
  183. RenameDialogText().Select(0, static_cast<int32_t>(name.rend() - it) - 1);
  184. }
  185. renameDialog.DataContext(item);
  186. renameDialog.ShowAsync();
  187. }
  188. fire_and_forget MainPage::ConfigDeleteMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
  189. {
  190. try {
  191. const auto lifetime = get_strong();
  192. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  193. if (item == nullptr) {
  194. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  195. }
  196. if (item.IsDefault()) {
  197. co_await NotifyUser(L"Default configuration cannot be deleted.");
  198. co_return;
  199. }
  200. uint32_t index;
  201. auto configItems = ConfigItems();
  202. if (!configItems.IndexOf(item, index)) {
  203. co_return;
  204. }
  205. ContentDialog c{};
  206. c.Title(box_value(L"Delete this configuration file?"));
  207. c.Content(box_value(L"This operation cannot be undone."));
  208. c.PrimaryButtonCommand(StandardUICommand{ StandardUICommandKind::Delete });
  209. c.SecondaryButtonCommand(StandardUICommand{ StandardUICommandKind::Close });
  210. const auto& result = co_await c.ShowAsync();
  211. if (result != ContentDialogResult::Primary) {
  212. co_return;
  213. }
  214. if (ConfigListView().SelectedItem() == item) {
  215. ConfigListView().SelectedIndex(ConfigListView().SelectedIndex() == 0 ? 1 : 0);
  216. }
  217. configItems.RemoveAt(index);
  218. co_await item.Delete();
  219. if (configItems.Size() == 0) {
  220. LoadConfigs();
  221. }
  222. }
  223. catch (...)
  224. {
  225. UI::NotifyException(L"Deleting file");
  226. }
  227. }
  228. void MainPage::RenameDialogPrimaryButton_Click(IInspectable const&, ContentDialogButtonClickEventArgs const&)
  229. {
  230. ConfirmRename();
  231. }
  232. void MainPage::RenameDialogText_KeyDown(IInspectable const&, KeyRoutedEventArgs const& e)
  233. {
  234. if (e.Key() == Windows::System::VirtualKey::Enter) {
  235. ConfirmRename();
  236. RenameDialog().Hide();
  237. }
  238. }
  239. fire_and_forget MainPage::ConfirmRename() {
  240. try {
  241. const auto lifetime = get_strong();
  242. const auto& renameDialog = RenameDialog();
  243. const auto& item = renameDialog.DataContext().as<Maple_App::ConfigViewModel>();
  244. if (item == nullptr) {
  245. co_return;
  246. }
  247. co_await item.Rename(RenameDialogText().Text());
  248. if (item == m_defaultConfig) {
  249. ApplicationData::Current().LocalSettings().Values().Insert(CONFIG_PATH_SETTING_KEY, box_value(item.File().Path()));
  250. }
  251. }
  252. catch (...)
  253. {
  254. UI::NotifyException(L"Renaming");
  255. }
  256. }
  257. fire_and_forget MainPage::ConfigCreateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  258. {
  259. try {
  260. const auto lifetime = get_strong();
  261. const auto& buttonText = sender.as<MenuFlyoutItem>().Text();
  262. StorageFile newFile{ nullptr };
  263. if (buttonText == L"Conf") {
  264. newFile = co_await CopyDefaultConfig(m_configFolder, DEFAULT_CONF_FILE_PATH, L"New Config.conf");
  265. }
  266. else if (buttonText == L"JSON") {
  267. newFile = co_await CopyDefaultConfig(m_configFolder, DEFAULT_JSON_FILE_PATH, L"New Config.json");
  268. }
  269. else if (buttonText == L"Minimal")
  270. {
  271. newFile = co_await CopyDefaultConfig(m_configFolder, DEFAULT_MINIMAL_FILE_PATH, L"New Config.conf");
  272. }
  273. else {
  274. co_return;
  275. }
  276. const auto& item = co_await ConfigViewModel::FromFile(newFile, false);
  277. ConfigItems().Append(item);
  278. RequestRenameItem(item);
  279. }
  280. catch (...)
  281. {
  282. UI::NotifyException(L"Creating file");
  283. }
  284. }
  285. fire_and_forget MainPage::ConfigImportMenuItem_Click(IInspectable const& sender, RoutedEventArgs const& e)
  286. {
  287. try {
  288. const auto lifetime = get_strong();
  289. bool unsnapped = ((ApplicationView::Value() != ApplicationViewState::Snapped) || ApplicationView::TryUnsnap());
  290. if (!unsnapped)
  291. {
  292. co_await NotifyUser(L"Cannot unsnap the app.");
  293. co_return;
  294. }
  295. ImportFilePicker().FileTypeFilter().ReplaceAll({ L".conf", L".json", L".mmdb", L".dat", L".cer", L".crt" });
  296. const auto& files = co_await ImportFilePicker().PickMultipleFilesAsync();
  297. co_await ImportFiles(files);
  298. }
  299. catch (...)
  300. {
  301. UI::NotifyException(L"Importing files");
  302. }
  303. }
  304. fire_and_forget MainPage::ConfigDuplicateMenuItem_Click(IInspectable const& sender, RoutedEventArgs const&)
  305. {
  306. try {
  307. const auto lifetime = get_strong();
  308. auto item = sender.as<FrameworkElement>().DataContext().as<Maple_App::ConfigViewModel>();
  309. if (item == nullptr) {
  310. item = ConfigListView().SelectedItem().as<Maple_App::ConfigViewModel>();
  311. }
  312. const auto& file = item.File();
  313. const auto& parent = co_await file.GetParentAsync();
  314. const auto& newFile = co_await file.CopyAsync(parent, file.Name(), NameCollisionOption::GenerateUniqueName);
  315. ConfigItems().Append(co_await ConfigViewModel::FromFile(newFile, false));
  316. }
  317. catch (...)
  318. {
  319. UI::NotifyException(L"Duplicating");
  320. }
  321. }
  322. void MainPage::MainPivot_PivotItemLoaded(Pivot const&, PivotItemEventArgs const& args)
  323. {
  324. try {
  325. if (args.Item().Header().as<hstring>() == L"Setting") {
  326. const auto& netifs = Netif::EnumerateInterfaces();
  327. std::vector<IInspectable> boxed_netifs;
  328. boxed_netifs.reserve(netifs.size());
  329. std::transform(netifs.begin(), netifs.end(), std::back_inserter(boxed_netifs), [](const auto& netif) -> auto {
  330. return netif;
  331. });
  332. NetifCombobox().ItemsSource(single_threaded_vector(std::move(boxed_netifs)));
  333. const auto& currentNetif = ApplicationData::Current().LocalSettings().Values().TryLookup(NETIF_SETTING_KEY).try_as<hstring>();
  334. if (currentNetif.has_value()) {
  335. NetifCombobox().SelectedValue(box_value(currentNetif.value()));
  336. }
  337. else {
  338. const auto it = std::find_if(netifs.begin(), netifs.end(), [](const auto& netif) -> bool {
  339. return netif.Desc().size() > 0 && netif.Desc()[0] == L'★';
  340. });
  341. if (it != netifs.end()) {
  342. NetifCombobox().SelectedItem(*it);
  343. }
  344. }
  345. }
  346. }
  347. catch (...)
  348. {
  349. UI::NotifyException(L"Loading settings");
  350. }
  351. }
  352. void MainPage::NetifCombobox_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
  353. {
  354. try {
  355. const auto it = e.AddedItems().First();
  356. if (!it.HasCurrent() || it.Current().try_as<Maple_App::Netif>() == nullptr) {
  357. return;
  358. }
  359. const auto& netif = it.Current().as<Maple_App::Netif>();
  360. ApplicationData::Current().LocalSettings().Values().Insert(NETIF_SETTING_KEY, box_value(netif.Addr()));
  361. }
  362. catch (...)
  363. {
  364. UI::NotifyException(L"Setting interface");
  365. }
  366. }
  367. void MainPage::ConfigListView_SelectionChanged(IInspectable const&, SelectionChangedEventArgs const& e)
  368. {
  369. try {
  370. const auto& item = e.AddedItems().First().Current();
  371. auto targetPage = xaml_typename<MonacoEditPage>();
  372. const auto& config = item.try_as<Maple_App::ConfigViewModel>();
  373. if (config != nullptr) {
  374. const auto ext = getNormalizedExtentionFromPath(config.Name());
  375. if (ext == ".mmdb") {
  376. targetPage = xaml_typename<MmdbPage>();
  377. }
  378. else if (ext == ".dat") {
  379. targetPage = xaml_typename<DatPage>();
  380. }
  381. else if (ext == ".cer" || ext == ".crt") {
  382. targetPage = xaml_typename<CertPage>();
  383. }
  384. }
  385. if (targetPage.Name != xaml_typename<MonacoEditPage>().Name)
  386. {
  387. MainContentFrame().BackStack().Clear();
  388. }
  389. MainContentFrame().Navigate(targetPage, item);
  390. }
  391. catch (...)
  392. {
  393. UI::NotifyException(L"Opening file");
  394. }
  395. }
  396. void MainPage::ConfigListView_DragItemsStarting(IInspectable const&, DragItemsStartingEventArgs const& e)
  397. {
  398. try {
  399. std::vector<IStorageItem> files;
  400. files.reserve(static_cast<size_t>(e.Items().Size()));
  401. for (const auto& obj : e.Items()) {
  402. const auto& item = obj.try_as<Maple_App::ConfigViewModel>();
  403. if (item == nullptr) {
  404. continue;
  405. }
  406. files.push_back(item.File());
  407. }
  408. const auto& data = e.Data();
  409. data.SetStorageItems(files);
  410. data.RequestedOperation(DataPackageOperation::Copy);
  411. }
  412. catch (...)
  413. {
  414. UI::NotifyException(L"Preparing drag items");
  415. }
  416. }
  417. void MainPage::ConfigListView_DragOver(IInspectable const&, DragEventArgs const& e)
  418. {
  419. try {
  420. if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
  421. || !e.DataView().Contains(StandardDataFormats::StorageItems())) {
  422. e.AcceptedOperation(DataPackageOperation::None);
  423. return;
  424. }
  425. e.AcceptedOperation(DataPackageOperation::Copy);
  426. }
  427. catch (...)
  428. {
  429. UI::NotifyException(L"Dragging");
  430. }
  431. }
  432. fire_and_forget MainPage::ConfigListView_Drop(IInspectable const&, DragEventArgs const& e)
  433. {
  434. try {
  435. const auto lifetime = get_strong();
  436. const auto& dataView = e.DataView();
  437. if (static_cast<uint32_t>(e.AllowedOperations() & DataPackageOperation::Copy) == 0
  438. || !dataView.Contains(StandardDataFormats::StorageItems())) {
  439. co_return;
  440. }
  441. const auto& items = co_await dataView.GetStorageItemsAsync();
  442. co_await ImportFiles(items);
  443. }
  444. catch (...)
  445. {
  446. UI::NotifyException(L"Pasting files");
  447. }
  448. }
  449. void MainPage::WindowWidth_CurrentStateChanged(IInspectable const&, VisualStateChangedEventArgs const& e)
  450. {
  451. const auto& state = e.NewState();
  452. NavigationManager.AppViewBackButtonVisibility(state == nullptr
  453. ? AppViewBackButtonVisibility::Visible
  454. : AppViewBackButtonVisibility::Collapsed);
  455. }
  456. void MainPage::MainSplitView_PaneClosing(SplitView const&, SplitViewPaneClosingEventArgs const&)
  457. {
  458. NavigationManager.AppViewBackButtonVisibility(AppViewBackButtonVisibility::Visible);
  459. }
  460. fire_and_forget MainPage::GenerateProfileButton_Click(IInspectable const& sender, RoutedEventArgs const& e)
  461. {
  462. try {
  463. const auto lifetime = get_strong();
  464. const auto& profile = VpnPlugInProfile{};
  465. profile.AlwaysOn(false);
  466. profile.ProfileName(L"Maple");
  467. profile.RequireVpnClientAppUI(true);
  468. profile.VpnPluginPackageFamilyName(Windows::ApplicationModel::Package::Current().Id().FamilyName());
  469. profile.RememberCredentials(false);
  470. profile.ServerUris().Append(Uri{ L"https://github.com/YtFlow/Maple" });
  471. const auto& result = co_await VpnMgmtAgent.AddProfileFromObjectAsync(profile);
  472. if (result == VpnManagementErrorStatus::Ok) {
  473. co_await NotifyUser(L"Profile generated.");
  474. }
  475. else {
  476. co_await NotifyUser(L"Failed to generate a profile (" + to_hstring(static_cast<int32_t>(result)) + L").");
  477. }
  478. }
  479. catch (...)
  480. {
  481. UI::NotifyException(L"Generating profile");
  482. }
  483. }
  484. fire_and_forget MainPage::ConnectionToggleSwitch_Toggled(IInspectable const&, RoutedEventArgs const&)
  485. {
  486. try {
  487. const auto lifetime{ get_strong() };
  488. if (!ApplicationData::Current().LocalSettings().Values().HasKey(NETIF_SETTING_KEY)) {
  489. MainPivot().SelectedIndex(1);
  490. co_await 400ms;
  491. co_await resume_foreground(Dispatcher());
  492. NetifCombobox().IsDropDownOpen(true);
  493. co_return;
  494. }
  495. const auto connect = ConnectionToggleSwitch().IsOn();
  496. ConnectionToggleSwitch().IsEnabled(false);
  497. VpnManagementErrorStatus status = VpnManagementErrorStatus::Ok;
  498. if (connect) {
  499. status = co_await VpnMgmtAgent.ConnectProfileAsync(m_vpnProfile);
  500. }
  501. else {
  502. status = co_await VpnMgmtAgent.DisconnectProfileAsync(m_vpnProfile);
  503. }
  504. if (status == VpnManagementErrorStatus::Ok)
  505. {
  506. ConnectionToggleSwitch().IsEnabled(true);
  507. }
  508. else {
  509. NotifyUser(L"Could not perform the requested operation. Please try again from system VPN settings for detailed error messages.");
  510. }
  511. }
  512. catch (...)
  513. {
  514. UI::NotifyException(L"Connecting");
  515. }
  516. }
  517. fire_and_forget MainPage::StartConnectionCheck()
  518. {
  519. try {
  520. const auto lifetime{ get_strong() };
  521. IVectorView<IVpnProfile> profiles{ nullptr };
  522. auto event_token{ ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled }) };
  523. while (true) {
  524. if (m_vpnProfile == nullptr) {
  525. profiles = co_await VpnMgmtAgent.GetProfilesAsync();
  526. for (auto const p : profiles) {
  527. if (p.ProfileName() == L"Maple" || p.ProfileName() == L"maple") {
  528. m_vpnProfile = p.try_as<VpnPlugInProfile>();
  529. break;
  530. }
  531. }
  532. }
  533. if (m_vpnProfile == nullptr) {
  534. ConnectionToggleSwitch().IsEnabled(false);
  535. }
  536. else {
  537. ToolTipService::SetToolTip(ConnectionToggleSwitchContainer(), nullptr);
  538. auto status = VpnManagementConnectionStatus::Disconnected;
  539. try {
  540. status = m_vpnProfile.ConnectionStatus();
  541. }
  542. catch (...) {}
  543. ConnectionToggleSwitch().IsEnabled(status == VpnManagementConnectionStatus::Connected
  544. || status == VpnManagementConnectionStatus::Disconnected);
  545. ConnectionToggleSwitch().Toggled(event_token);
  546. ConnectionToggleSwitch().IsOn(status == VpnManagementConnectionStatus::Connected
  547. || status == VpnManagementConnectionStatus::Connecting);
  548. event_token = ConnectionToggleSwitch().Toggled({ this, &MainPage::ConnectionToggleSwitch_Toggled });
  549. }
  550. co_await 1s;
  551. co_await resume_foreground(Dispatcher());
  552. }
  553. }
  554. catch (...)
  555. {
  556. UI::NotifyException(L"Checking VPN status");
  557. }
  558. }
  559. }