MainPage.cpp 26 KB

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