MainPage.cpp 24 KB

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